(function () {
  if (window.WebVideoCtrl) {
    return
  }
  const WebVideoCtrl = (function () {
    const m_szWidth = '100%'
    const m_szHeight = '100%'
    const m_options = {
      szversion: 'V3.3.0 build20230314',
      szContainerID: '',
      szColorProperty: '',
      szBasePath: '',
      iWndowType: 1,
      bWndFull: true,
      iPackageType: 2,
      bDebugMode: true,
      cbSelWnd: null,
      cbDoubleClickWnd: null,
      cbEvent: null,
      cbInitPluginComplete: null
    }
    let m_pluginOBJECT = null
    let m_iSelWnd = 0
    let m_bFullScreen = false
    const m_deviceSet = []
    const m_wndSet = []
    let m_ISAPIProtocol = null
    let m_utilsInc = null
    const m_webVideoCtrl = this
    let m_oLocalCfg = null
    const PROTOCOL_DEVICE_ISAPI = 1
    const ERROR_CODE_UNKNOWN = 1e3
    const ERROR_CODE_NETWORKERROR = 1001
    const ERROR_CODE_PARAMERROR = 1002
    const ERROR_CODE_LOGIN_NOLOGIN = 2e3
    const ERROR_CODE_LOGIN_REPEATLOGIN = 2001
    const ERROR_CODE_LOGIN_NOSUPPORT = 2002
    const ERROR_CODE_PLAY_PLUGININITFAIL = 3e3
    const ERROR_CODE_PLAY_NOREPEATPLAY = 3001
    const ERROR_CODE_PLAY_PLAYBACKABNORMAL = 3002
    const ERROR_CODE_PLAY_PLAYBACKSTOP = 3003
    const ERROR_CODE_PLAY_NOFREESPACE = 3004
    const ERROR_CODE_TALK_FAIL = 5e3
    const HTTP_STATUS_OK_200 = 200
    const HTTP_STATUS_ERROR_403 = 403
    const PLAY_STATUS_STOP = 0
    const PLAY_STATUS_REALPLAY = 1
    const PLAY_STATUS_PLAYBACK = 2
    const PLAY_STATUS_PAUSE = 3
    const PLAY_STATUS_FRAME = 4
    const PLAY_STATUS_REVERSE_PLAYBACK = 5
    const PLAY_STATUS_REVERSE_PAUSE = 6
    const PROTOCOLTYPE_PLAY_TCP = 0
    const PROTOCOLTYPE_PLAY_UDP = 1
    const DEVICE_TYPE_IPCAMERA = 'IPCamera'
    const DEVICE_TYPE_IPDOME = 'IPDome'
    const DEVICE_TYPE_IPZOOM = 'IPZoom'
    const DEVICE_TYPE_GATEWAY = 'Gateway'
    const m_szVersion =
      "<?xml version='1.0' encoding='utf-8'?><FileVersion>" +
      "<Platform name='win32'>" +
      '<localServiceControl>1.0.0.40</localServiceControl>'
    '</Platform>' + '</FileVersion>'
    const _onGetSelectWndInfo = function (iWnd) {
      m_iSelWnd = iWnd
      if (m_options.cbSelWnd) {
        const arrXml = []
        arrXml.push('<RealPlayInfo>')
        arrXml.push('<SelectWnd>' + m_iSelWnd + '</SelectWnd>')
        arrXml.push('</RealPlayInfo>')
        m_options.cbSelWnd(m_utilsInc.loadXML(arrXml.join('')))
      }
    }
    const _onMouseEvent = function (oData) {
      if (m_options.cbDoubleClickWnd && oData.eventType === 2) {
        if (m_options.bWndFull) {
          const iIndex = m_webVideoCtrl.findWndIndexByIndex(oData.wndIndex)
          if (iIndex != -1) {
            m_bFullScreen = !m_bFullScreen
          }
        }
        m_options.cbDoubleClickWnd(oData.wndIndex, m_bFullScreen)
      }
    }
    const _onPluginEventHandler = function (iWndIndex, iErrorCode, oError) {
      let iNewError = ERROR_CODE_UNKNOWN
      if (iErrorCode === 0) {
        iNewError = ERROR_CODE_PLAY_PLAYBACKABNORMAL
      } else if (iErrorCode === 2) {
        iNewError = ERROR_CODE_PLAY_PLAYBACKSTOP
      } else if (iErrorCode === 3) {
        iNewError = ERROR_CODE_TALK_FAIL
      } else if (iErrorCode === 21) {
        iNewError = ERROR_CODE_PLAY_NOFREESPACE
      }
      if (
        ERROR_CODE_PLAY_PLAYBACKABNORMAL == iNewError ||
        ERROR_CODE_PLAY_PLAYBACKSTOP == iNewError
      ) {
        m_webVideoCtrl.I_Stop(iWndIndex)
      } else if (ERROR_CODE_PLAY_NOFREESPACE == iNewError) {
        m_webVideoCtrl.I_StopRecord(iWndIndex)
      } else if (ERROR_CODE_TALK_FAIL == iNewError) {
        m_webVideoCtrl.I_StopVoiceTalk()
      } else {
      }
      if (m_options.cbEvent) {
        m_options.cbEvent(iNewError, iWndIndex, oError)
      }
    }
    const _onKeyBoardEvent = function (iKeyCode) {
      if (parseInt(iKeyCode, 10) === 100) {
        m_bFullScreen = false
        if (m_options.cbDoubleClickWnd) {
          m_options.cbDoubleClickWnd(m_iSelWnd, m_bFullScreen)
        }
      }
    }
    const _onZoomInfoCallback = function (oPoints) {
      let iIndex = m_webVideoCtrl.findWndIndexByIndex(m_iSelWnd)
      if (iIndex != -1) {
        const oWndInfo = m_wndSet[iIndex]
        iIndex = m_webVideoCtrl.findDeviceIndexByIP(oWndInfo.szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc.set3DZoom(
            oDeviceInfo,
            oWndInfo,
            oPoints,
            {}
          )
        }
      }
    }
    const _oNoLoginError = {
      errorCode: ERROR_CODE_LOGIN_NOLOGIN,
      errorMsg: 'The device is not login.'
    }
    const _oUnKnownError = {
      errorCode: ERROR_CODE_UNKNOWN,
      errorMsg: 'Unknown error.'
    }
    const _oParamsError = {
      errorCode: ERROR_CODE_PARAMERROR,
      errorMsg: 'Params error.'
    }
    const _printString = function () {
      if (m_options.bDebugMode) {
        const printString = m_utilsInc.formatString(arguments)
        console.log(printString)
      }
    }
    const _initLocalCfg = function () {
      const oPromise = new Promise(function (resolve, reject) {
        m_pluginOBJECT.JS_GetLocalConfig().then(
          oLocalCofing => {
            m_oLocalCfg = oLocalCofing
            resolve()
          },
          () => {
            reject()
          }
        )
      })
      return oPromise
    }
    const _initDeviceInfo = function (oDeviceInfo) {
      const oPromise = new Promise(function (resolve, reject) {
        const oP1 = oDeviceInfo.oProtocolInc.getDeviceInfo(oDeviceInfo, {})
        const oP2 = oDeviceInfo.oProtocolInc.getAnalogChannelInfo(
          oDeviceInfo,
          {}
        )
        const oP3 = oDeviceInfo.oProtocolInc.getAudioInfo(oDeviceInfo, {})
        const oP4 = _getPort(oDeviceInfo)
        const oP5 = oDeviceInfo.oProtocolInc.getDeviceMinusLocalTime(
          oDeviceInfo
        )
        Promise.all([oP1, oP2, oP3, oP4, oP5]).then(
          () => {
            resolve()
          },
          () => {
            resolve()
          }
        )
      })
      return oPromise
    }
    const _initPlugin = function (szContainerID) {
      const oPromise = new Promise(function (resolve, reject) {
        if (!m_utilsInc.isUndefined(szContainerID)) {
          m_options.szContainerID = szContainerID
        }
        if (document.getElementById(m_options.szContainerID) == null) {
          reject(_oParamsError)
          return
        }
        const oParam = {
          szId: szContainerID,
          iType: 1,
          iWidth: m_szWidth,
          iHeight: m_szHeight,
          iMaxSplit: 4,
          iCurrentSplit: m_options.iWndowType,
          iServicePortStart: 34686,
          iServicePortEnd: 34690,
          oSessionInfo: {
            sessionID:
              '11c12b3257f037bb50052db3ac5e342572c3d963622baca122755c482ce8823a',
            user: 'admin',
            challenge: '275816f02ec2dca22b6a6ae87c7cb7e3',
            iterations: 100,
            random: '34765058'
          },
          iPluginType: 2,
          onConnectSuccess: () => {
            const oElem = $('#' + szContainerID)
            m_pluginOBJECT.JS_Resize(oElem.width(), oElem.height())
            if (m_pluginOBJECT.iPluginMode !== 2) {
              reject({
                errorCode: ERROR_CODE_PLAY_PLUGININITFAIL,
                errorMsg: 'Plugin init failed.'
              })
              return
            }
            const iWndFull = m_options.bWndFull ? 1 : 0
            m_pluginOBJECT.JS_SetFullScreenCapability(iWndFull)
            m_pluginOBJECT.JS_SetPackageType(m_options.iPackageType)
            _initPluginEvent()
            _initLocalCfg().then(() => {
              resolve()
            })
          },
          onConnectError: () => {
            reject({
              errorCode: ERROR_CODE_PLAY_PLUGININITFAIL,
              errorMsg: 'Plugin init failed.'
            })
          },
          szBasePath: m_utilsInc.getDirName()
        }
        m_pluginOBJECT = new JSVideoPlugin(oParam)
      })
      return oPromise
    }
    var _initPluginEvent = function () {
      m_pluginOBJECT.JS_SetWindowControlCallback({
        onGetSelectWndInfo: iwnd => {
          _onGetSelectWndInfo(iwnd)
        },
        onPluginEventHandler: (iWndIndex, iEventType, iParam2) => {
          _onPluginEventHandler(iWndIndex, iEventType, iParam2)
        },
        KeyBoardEvent: szXml => {
          _onKeyBoardEvent(szXml)
        },
        onMouseEvent: function (oData) {
          _onMouseEvent(oData)
        }
      })
    }
    var _getPort = function (oDeviceInfo) {
      const oPromise = new Promise(async (resolve, reject) => {
        let oPort = null
        const bPPPoE = await _getPPPoEEnable(oDeviceInfo)
        if (bPPPoE) {
          oPort = await _getInternalPort(oDeviceInfo)
        } else {
          const ipset = await _getDeviceIPAddr(oDeviceInfo)
          let bSame = false
          for (let i = 0; i < ipset.length; i++) {
            if (
              ipset[i].ipv4 == oDeviceInfo.szIP ||
              ipset[i].ipv6 == oDeviceInfo.szIP
            ) {
              bSame = true
              break
            }
          }
          if (bSame) {
            oPort = await _getInternalPort(oDeviceInfo)
          } else {
            oPort = await _getExternalPort(oDeviceInfo)
            if (oPort.iRtspPort == -1 && oPort.iDevicePort == -1) {
              oPort = await _getInternalPort(oDeviceInfo)
            }
          }
        }
        oDeviceInfo.iRtspPort = oPort.iRtspPort
        oDeviceInfo.iHttpPort = oPort.iHttpPort
        resolve(oPort)
      })
      return oPromise
    }
    var _getInternalPort = function (oDeviceInfo) {
      const oPromise = new Promise((resolve, reject) => {
        let iRtspPort = -1
        let iHttpPort = -1
        let iDevicePort = -1
        oDeviceInfo.oProtocolInc.getPortInfo(oDeviceInfo, {
          async: false,
          success: function (xmlDoc) {
            const nodeList = NS.$XML(xmlDoc).find('AdminAccessProtocol', true)
            iRtspPort = 554
            for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
              if (
                NS.$XML(nodeList)
                  .eq(i)
                  .find('protocol')
                  .eq(0)
                  .text()
                  .toLowerCase() === 'rtsp'
              ) {
                iRtspPort = parseInt(
                  NS.$XML(nodeList)
                    .eq(i)
                    .find('portNo')
                    .eq(0)
                    .text(),
                  10
                )
              }
              if (
                NS.$XML(nodeList)
                  .eq(i)
                  .find('protocol')
                  .eq(0)
                  .text()
                  .toLowerCase() === 'http'
              ) {
                iHttpPort = parseInt(
                  NS.$XML(nodeList)
                    .eq(i)
                    .find('portNo')
                    .eq(0)
                    .text(),
                  10
                )
              }
              if (
                NS.$XML(nodeList)
                  .eq(i)
                  .find('protocol')
                  .eq(0)
                  .text()
                  .toLowerCase() === 'dev_manage'
              ) {
                iDevicePort = parseInt(
                  NS.$XML(nodeList)
                    .eq(i)
                    .find('portNo')
                    .eq(0)
                    .text(),
                  10
                )
              }
            }
            resolve({
              iRtspPort: iRtspPort,
              iHttpPort: iHttpPort,
              iDevicePort: iDevicePort
            })
          },
          error: function () {
            resolve({ iRtspPort: -1, iHttpPort: -1, iDevicePort: -1 })
          }
        })
      })
      return oPromise
    }
    var _getExternalPort = function (oDeviceInfo) {
      const oPromise = new Promise((resolve, reject) => {
        let iRtspPort = -1
        let iHttpPort = -1
        let iDevicePort = -1
        oDeviceInfo.oProtocolInc.getUPnPPortStatus(oDeviceInfo, {
          async: false,
          success: function (xmlDoc) {
            const nodeList = NS.$XML(xmlDoc).find('portStatus', true)
            for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
              if (
                NS.$XML(nodeList)
                  .eq(i)
                  .find('internalPort')
                  .eq(0)
                  .text()
                  .toLowerCase() == 'rtsp'
              ) {
                iRtspPort = parseInt(
                  NS.$XML(nodeList)
                    .eq(i)
                    .find('externalPort')
                    .eq(0)
                    .text(),
                  10
                )
              }
              if (
                NS.$XML(nodeList)
                  .eq(i)
                  .find('internalPort')
                  .eq(0)
                  .text()
                  .toLowerCase() == 'http'
              ) {
                iHttpPort = parseInt(
                  NS.$XML(nodeList)
                    .eq(i)
                    .find('externalPort')
                    .eq(0)
                    .text(),
                  10
                )
              }
              if (
                NS.$XML(nodeList)
                  .eq(i)
                  .find('internalPort')
                  .eq(0)
                  .text()
                  .toLowerCase() == 'admin'
              ) {
                iDevicePort = parseInt(
                  NS.$XML(nodeList)
                    .eq(i)
                    .find('externalPort')
                    .eq(0)
                    .text(),
                  10
                )
              }
            }
            resolve({
              iRtspPort: iRtspPort,
              iHttpPort: iHttpPort,
              iDevicePort: iDevicePort
            })
          },
          error: function () {
            resolve({ iRtspPort: -1, iHttpPort: -1, iDevicePort: -1 })
          }
        })
      })
      return oPromise
    }
    var _getDeviceIPAddr = function (oDeviceInfo) {
      const oPromise = new Promise(function (resolve) {
        const arrIP = []
        oDeviceInfo.oProtocolInc.getNetworkBond(oDeviceInfo, {
          async: false,
          success: function (xmlDoc) {
            if (
              NS.$XML(xmlDoc)
                .find('enabled')
                .eq(0)
                .text() == 'true'
            ) {
              arrIP.push({
                ipv4: NS.$XML(xmlDoc)
                  .find('ipAddress')
                  .eq(0)
                  .text(),
                ipv6: NS.$XML(xmlDoc)
                  .find('ipv6Address')
                  .eq(0)
                  .text()
              })
              resolve(arrIP)
            } else {
              oDeviceInfo.oProtocolInc.getNetworkInterface(oDeviceInfo, {
                async: false,
                success: function (xmlDoc) {
                  const nodeList = NS.$XML(xmlDoc).find(
                    'NetworkInterface',
                    true
                  )
                  for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
                    arrIP.push({
                      ipv4: NS.$XML(xmlDoc)
                        .find('ipAddress')
                        .eq(0)
                        .text(),
                      ipv6: NS.$XML(xmlDoc)
                        .find('ipv6Address')
                        .eq(0)
                        .text()
                    })
                    break
                  }
                  resolve(arrIP)
                },
                error: function () {
                  resolve(arrIP)
                }
              })
            }
          },
          error: function () {
            oDeviceInfo.oProtocolInc.getNetworkInterface(oDeviceInfo, {
              async: false,
              success: function (xmlDoc) {
                const nodeList = NS.$XML(xmlDoc).find('NetworkInterface', true)
                for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
                  arrIP.push({
                    ipv4: NS.$XML(xmlDoc)
                      .find('ipAddress')
                      .eq(0)
                      .text(),
                    ipv6: NS.$XML(xmlDoc)
                      .find('ipv6Address')
                      .eq(0)
                      .text()
                  })
                  break
                }
                resolve(arrIP)
              },
              error: function () {
                resolve(arrIP)
              }
            })
          }
        })
      })
      return oPromise
    }
    var _getPPPoEEnable = function (oDeviceInfo) {
      const oPromise = new Promise(function (resolve) {
        let bEnabled = false
        oDeviceInfo.oProtocolInc.getPPPoEStatus(oDeviceInfo, {
          success: function (xmlDoc) {
            if (NS.$XML(xmlDoc).find('ipAddress', true).length > 0) {
              bEnabled = true
            } else if (NS.$XML(xmlDoc).find('ipv6Address', true).length > 0) {
              bEnabled = true
            } else {
              bEnabled = false
            }
            resolve(bEnabled)
          },
          error: function () {
            bEnabled = false
            resolve(bEnabled)
          }
        })
      })
      return oPromise
    }
    const _generateTransCodeXml = function (oTransCodeParam) {
      const oDefaultParam = {
        TransFrameRate: '',
        TransResolution: '',
        TransBitrate: ''
      }
      m_utilsInc.extend(oDefaultParam, oTransCodeParam)
      if (
        oDefaultParam.TransFrameRate == '' ||
        oDefaultParam.TransResolution == '' ||
        oDefaultParam.TransBitrate == ''
      ) {
        return ''
      }
      const ArraySet = []
      ArraySet.push("<?xml version='1.0' encoding='UTF-8'?>")
      ArraySet.push('<CompressionInfo>')
      ArraySet.push(
        '<TransFrameRate>' + oDefaultParam.TransFrameRate + '</TransFrameRate>'
      )
      ArraySet.push(
        '<TransResolution>' +
          oDefaultParam.TransResolution +
          '</TransResolution>'
      )
      ArraySet.push(
        '<TransBitrate>' + oDefaultParam.TransBitrate + '</TransBitrate>'
      )
      ArraySet.push('</CompressionInfo>')
      return ArraySet.join('')
    }
    const _setDeviceInfo = function (
      cgiInstance,
      oDeviceInfo,
      szIP,
      iProtocol,
      iPort,
      szUserName,
      szPassword
    ) {
      oDeviceInfo.szIP = szIP
      if (iProtocol == 2) {
        oDeviceInfo.szHttpProtocol = 'https://'
        oDeviceInfo.iHttpsPort = iPort
      } else {
        oDeviceInfo.szHttpProtocol = 'http://'
        oDeviceInfo.iHttpPort = iPort
      }
      oDeviceInfo.iCGIPort = iPort
      oDeviceInfo.szDeviceIdentify = szIP + '_' + iPort
      oDeviceInfo.iDeviceProtocol = PROTOCOL_DEVICE_ISAPI
      oDeviceInfo.oProtocolInc = cgiInstance
      oDeviceInfo.szAuth = m_utilsInc.Base64.encode(
        ':' + szUserName + ':' + szPassword
      )
    }
    const _doLogin = function (
      cgiInstance,
      oDeviceInfo,
      szIP,
      iProtocol,
      iPort,
      szUserName,
      szPassword,
      options
    ) {
      const newOptions = { success: null, error: null }
      m_utilsInc.extend(newOptions, options)
      m_utilsInc.extend(newOptions, {
        success: function (xmlDoc) {
          _setDeviceInfo(
            cgiInstance,
            oDeviceInfo,
            szIP,
            iProtocol,
            iPort,
            szUserName,
            szPassword
          )
          m_deviceSet.push(oDeviceInfo)
          _initDeviceInfo(oDeviceInfo).then(() => {
            if (options.success) {
              options.success(xmlDoc)
            }
          })
        },
        error: function (oError) {
          if (options.error) {
            options.error(oError)
          }
        }
      })
      return cgiInstance.digestLogin(
        szIP,
        iProtocol,
        iPort,
        szUserName,
        szPassword,
        newOptions
      )
    }
    this.I_SupportNoPlugin = function () {
      return false
    }
    this.I_Resize = function (iWidth, iHeight) {
      return m_pluginOBJECT.JS_Resize(iWidth, iHeight)
    }
    this.I_InitPlugin = function (options) {
      m_utilsInc.extend(m_options, options)
      const szDirName = m_utilsInc.getDirName()
      if (szDirName) {
        if (typeof exports === 'object' && typeof module !== 'undefined') {
        } else if (typeof define === 'function' && define.amd) {
          require([szDirName + '/jsVideoPlugin-1.0.0.min.js'], function (o) {
            window.JSVideoPlugin = o.JSVideoPlugin
            if (options.cbInitPluginComplete) {
              options.cbInitPluginComplete()
            }
          })
        } else {
          m_utilsInc.loadScript(
            szDirName + '/jsVideoPlugin-1.0.0.min.js',
            function () {
              if (options.cbInitPluginComplete) {
                options.cbInitPluginComplete()
              }
            }
          )
        }
      }
      window.addEventListener('resize', function () {
        if (m_pluginOBJECT !== null) {
          const oElem = $('#' + m_options.szContainerID)
          m_pluginOBJECT.JS_Resize(oElem.width(), oElem.height())
        }
      })
      window.addEventListener('unload', function () {})
    }

    /** 关闭播放插件 */
    this.I_StopWnd1 = function () {
      if (m_pluginOBJECT) {
        m_pluginOBJECT.JS_HideWnd()
        m_pluginOBJECT.JS_DestroyPlugin()
      }
    }

    this.I_InsertOBJECTPlugin = function (szContainerID) {
      return _initPlugin(szContainerID)
    }
    this.I_WriteOBJECT_XHTML = function () {
      return 0
    }
    this.I_OpenFileDlg = async function (iType) {
      const oPromise = new Promise(function (resolve, reject) {
        m_pluginOBJECT.JS_OpenFileBrowser(iType, '').then(
          szFilePath => {
            resolve(szFilePath)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetLocalCfg = function () {
      const oPromise = new Promise(function (resolve, reject) {
        m_pluginOBJECT.JS_GetLocalConfig().then(
          oLocalCofing => {
            resolve(oLocalCofing)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_SetLocalCfg = function (oLocalCofing) {
      const oPromise = new Promise(function (resolve, reject) {
        m_pluginOBJECT.JS_SetLocalConfig(oLocalCofing).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_Login = function (
      szIP,
      iProtocol,
      iPort,
      szUserName,
      szPassword,
      options
    ) {
      const oPromise = new Promise(function (resolve, reject) {
        const szDeviceIdentify = szIP + '_' + iPort
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          if (options.error) {
            options.error({
              errorCode: ERROR_CODE_LOGIN_REPEATLOGIN,
              errorMsg: 'The device is already login.'
            })
          }
          reject({
            errorCode: ERROR_CODE_LOGIN_REPEATLOGIN,
            errorMsg: 'The device is already login.'
          })
          return
        }
        const cgiInstance = m_ISAPIProtocol
        const oDeviceInfo = new deviceInfoClass()
        _doLogin(
          cgiInstance,
          oDeviceInfo,
          szIP,
          iProtocol,
          iPort,
          szUserName,
          szPassword,
          options
        ).then(
          () => {
            resolve()
          },
          oError => {
            reject(oError)
          }
        )
      })
      return oPromise
    }
    this.I_Logout = function (szDeviceIdentify) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          m_deviceSet.splice(iIndex, 1)
          resolve()
        }
      })
      return oPromise
    }
    this.I_GetAudioInfo = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc.getAudioInfo(oDeviceInfo, options).then(
            oData => {
              resolve(oData)
            },
            oError => {
              reject(oError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_GetDeviceInfo = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc.getDeviceInfo(oDeviceInfo, options).then(
            oData => {
              resolve(oData)
            },
            oError => {
              reject(oError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_GetAnalogChannelInfo = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc
            .getAnalogChannelInfo(oDeviceInfo, options)
            .then(
              oData => {
                resolve(oData)
              },
              oError => {
                reject(oError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_GetDigitalChannelInfo = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc
            .getDigitalChannelInfo(oDeviceInfo, options)
            .then(
              oData => {
                resolve(oData)
              },
              oError => {
                reject(oError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_GetZeroChannelInfo = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc
            .getZeroChannelInfo(oDeviceInfo, options)
            .then(
              oData => {
                resolve(oData)
              },
              oError => {
                reject(oError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_StartRealPlay = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        const newOptions = {
          iWndIndex: m_iSelWnd,
          iStreamType: 1,
          iChannelID: 1,
          bZeroChannel: false
        }
        m_utilsInc.extend(newOptions, options)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          const iWndIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
          if (iWndIndex == -1) {
            oDeviceInfo.oProtocolInc
              .startRealPlay(oDeviceInfo, newOptions)
              .then(
                function () {
                  if (options.success) {
                    options.success()
                  }
                  resolve()
                },
                function () {
                  if (options.error) {
                    options.error(_oUnKnownError)
                  }
                  reject(_oUnKnownError)
                }
              )
          } else {
            reject({
              errorCode: ERROR_CODE_PLAY_NOREPEATPLAY,
              errorMsg: 'The window is already playing.'
            })
          }
        } else {
          if (options.error) {
            options.error(_oNoLoginError)
          }
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_StartPlay = function (szDeviceIdentify, options) {
      const oPromise = new Promise(async function (resolve, reject) {
        let iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        const newOptions = { iWndIndex: m_iSelWnd }
        m_utilsInc.extend(newOptions, options)
        const oDeviceInfo = m_deviceSet[iIndex]
        iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex == -1) {
          oDeviceInfo.oProtocolInc.startPlay(oDeviceInfo, newOptions).then(
            function () {
              if (options.success) {
                options.success()
              }
              resolve()
            },
            function () {
              if (options.error) {
                options.error(_oUnKnownError)
              }
              reject(_oUnKnownError)
            }
          )
        } else {
          reject({
            errorCode: ERROR_CODE_PLAY_NOREPEATPLAY,
            errorMsg: 'The window is already playing.'
          })
        }
      })
      return oPromise
    }
    this.I_SetSecretKey = function (szSecretKey) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_SetSecretKey(0, szSecretKey, 1).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetEncryptString = function (szSecretKey) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_GetEncryptString(3, szSecretKey).then(
          szEncode => {
            resolve(szEncode)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_Stop = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.bRecord) {
            m_pluginOBJECT.JS_StopSave(wndInfo.iIndex)
          }
          if (wndInfo.bSound) {
            m_pluginOBJECT.JS_CloseSound()
          }
          if (wndInfo.bEZoom) {
            m_pluginOBJECT.JS_DisableZoom(wndInfo.iIndex)
          }
          m_pluginOBJECT.JS_Stop(newOptions.iWndIndex).then(
            () => {
              m_wndSet.splice(iIndex, 1)
              if (newOptions.success) {
                newOptions.success()
              }
              resolve()
            },
            () => {
              if (newOptions.error) {
                newOptions.error(_oUnKnownError)
              }
              reject(_oUnKnownError)
            }
          )
        } else {
          resolve()
        }
      })
      return oPromise
    }
    this.I_StopAllPlay = function () {
      const oPromise = new Promise(async function (resolve, reject) {
        m_pluginOBJECT.JS_StopRealPlayAll().then(
          () => {
            m_wndSet.length = 0
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_OpenSound = function (iWndIndex) {
      iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (!wndInfo.bSound) {
            m_pluginOBJECT.JS_OpenSound(iWndIndex).then(
              () => {
                wndInfo.bSound = true
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
          } else {
            reject(_oUnKnownError)
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_CloseSound = function (iWndIndex) {
      iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.bSound) {
            m_pluginOBJECT.JS_CloseSound().then(
              () => {
                wndInfo.bSound = false
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
          } else {
            reject(_oUnKnownError)
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_SetVolume = function (iVolume, iWndIndex) {
      const oPromise = new Promise((resolve, reject) => {
        const iRet = -1
        iVolume = parseInt(iVolume, 10)
        if (isNaN(iVolume)) {
          reject(_oParamsError)
          return
        }
        if (iVolume < 0 || iVolume > 100) {
          reject(_oParamsError)
          return
        }
        iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          m_pluginOBJECT.JS_SetVolume(iWndIndex, iVolume).then(
            () => {
              resolve()
            },
            () => {
              reject(_oUnKnownError)
            }
          )
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_CapturePic = function (szPicName, options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { iWndIndex: m_iSelWnd, bDateDir: true }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          if (szPicName.slice(-4).toLowerCase() === '.jpg') {
            szPicName = szPicName.slice(0, -4)
          } else if (szPicName.slice(-5).toLowerCase() === '.jpeg') {
            szPicName = szPicName.slice(0, -5)
          }
          m_pluginOBJECT
            .JS_CapturePicture(
              newOptions.iWndIndex,
              szPicName,
              newOptions.bDateDir
            )
            .then(
              () => {
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_CapturePicData = function (options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { iWndIndex: m_iSelWnd, bDateDir: true }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          m_pluginOBJECT.JS_GetCaptureData(newOptions.iWndIndex).then(
            function (data) {
              resolve(data)
            },
            function (data) {
              reject(_oUnKnownError)
            }
          )
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_StartRecord = function (szFileName, options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { iWndIndex: m_iSelWnd, bDateDir: true }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (!wndInfo.bRecord) {
            m_pluginOBJECT.JS_StartSave(newOptions.iWndIndex, szFileName).then(
              function () {
                wndInfo.bRecord = true
                if (newOptions.success) {
                  newOptions.success()
                }
                resolve()
              },
              function () {
                if (newOptions.error) {
                  newOptions.error(_oUnKnownError)
                }
                reject(_oUnKnownError)
              }
            )
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
          }
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_StopRecord = function (options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.bRecord) {
            m_pluginOBJECT.JS_StopSave(newOptions.iWndIndex).then(
              function () {
                wndInfo.bRecord = false
                if (newOptions.success) {
                  newOptions.success()
                }
                resolve()
              },
              function () {
                if (newOptions.error) {
                  newOptions.error(_oUnKnownError)
                }
                reject(_oUnKnownError)
              }
            )
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
          }
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_StartVoiceTalk = function (szDeviceIdentify, iAudioChannel) {
      const oPromise = new Promise((resolve, reject) => {
        if (isNaN(parseInt(iAudioChannel, 10))) {
          reject(_oParamsError)
          return
        }
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          if (!oDeviceInfo.bVoiceTalk) {
            oDeviceInfo.oProtocolInc
              .startVoiceTalk(oDeviceInfo, iAudioChannel)
              .then(
                () => {
                  m_deviceSet[iIndex].bVoiceTalk = true
                  resolve()
                },
                () => {
                  reject(_oUnKnownError)
                }
              )
          } else {
            reject(_oUnKnownError)
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_StopVoiceTalk = function () {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_StopTalk().then(
          () => {
            for (let i = 0, iLen = m_deviceSet.length; i < iLen; i++) {
              if (m_deviceSet[i].bVoiceTalk) {
                m_deviceSet[i].bVoiceTalk = false
                break
              }
            }
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_StartAudioPlay = function (szDeviceIdentify, options) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          options.szAuth = oDeviceInfo.szAuth
          if (!oDeviceInfo.bVoiceTalk) {
            oDeviceInfo.oProtocolInc.audioPlay(options).then(
              () => {
                m_deviceSet[iIndex].bVoiceTalk = true
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
          } else {
            reject(_oUnKnownError)
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_StopAudioPlay = function () {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_StopAudioPlay().then(
          () => {
            for (let i = 0, iLen = m_deviceSet.length; i < iLen; i++) {
              if (m_deviceSet[i].bVoiceTalk) {
                m_deviceSet[i].bVoiceTalk = false
                break
              }
            }
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_PTZControl = function (iPTZIndex, bStop, options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = {
          iWndIndex: m_iSelWnd,
          iPTZIndex: iPTZIndex,
          iPTZSpeed: 4
        }
        m_utilsInc.extend(newOptions, options)
        let iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          iIndex = this.findDeviceIndexByIP(wndInfo.szIP)
          if (iIndex != -1) {
            const oDeviceInfo = m_deviceSet[iIndex]
            if (iPTZIndex == 9) {
              oDeviceInfo.oProtocolInc
                .ptzAutoControl(oDeviceInfo, bStop, wndInfo, newOptions)
                .then(
                  () => {
                    resolve()
                  },
                  oError => {
                    reject(oError)
                  }
                )
            } else {
              oDeviceInfo.oProtocolInc
                .ptzControl(oDeviceInfo, bStop, wndInfo, newOptions)
                .then(
                  () => {
                    resolve()
                  },
                  oError => {
                    reject(oError)
                  }
                )
            }
          }
        }
      })
      return oPromise
    }
    this.I_EnableEZoom = function (iWndIndex) {
      const oPromise = new Promise((resolve, reject) => {
        iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (!wndInfo.bEZoom) {
            m_pluginOBJECT.JS_EnableZoom(iWndIndex).then(
              () => {
                wndInfo.bEZoom = true
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_DisableEZoom = function (iWndIndex) {
      const oPromise = new Promise((resolve, reject) => {
        iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.bEZoom) {
            m_pluginOBJECT.JS_DisableZoom(iWndIndex).then(
              () => {
                wndInfo.bEZoom = false
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
          } else {
            resolve()
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_Enable3DZoom = function (iWndIndex) {
      const oPromise = new Promise((resolve, reject) => {
        iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (!wndInfo.b3DZoom) {
            m_pluginOBJECT.JS_SetDrawCallback(
              iWndIndex,
              true,
              'Rect',
              false,
              function (oRect) {
                _onZoomInfoCallback(oRect.points)
              }
            )
            wndInfo.b3DZoom = true
            resolve()
          } else {
            resolve()
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_Disable3DZoom = function (iWndIndex) {
      const oPromise = new Promise((resolve, reject) => {
        iWndIndex = m_utilsInc.isUndefined(iWndIndex) ? m_iSelWnd : iWndIndex
        const iIndex = this.findWndIndexByIndex(iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.b3DZoom) {
            m_pluginOBJECT.JS_SetDrawCallback(
              iWndIndex,
              false,
              'Rect',
              false,
              function () {}
            )
            wndInfo.b3DZoom = false
            resolve()
          } else {
            resolve()
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_FullScreen = function (bFull) {
      const oPromise = new Promise(function (resolve, reject) {
        m_pluginOBJECT.JS_FullScreenDisplay(bFull).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_SetPreset = function (iPresetID, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd, iPresetID: iPresetID }
        m_utilsInc.extend(newOptions, options)
        let iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          iIndex = this.findDeviceIndexByIP(wndInfo.szIP)
          if (iIndex != -1) {
            const oDeviceInfo = m_deviceSet[iIndex]
            oDeviceInfo.oProtocolInc
              .setPreset(oDeviceInfo, wndInfo, newOptions)
              .then(
                () => {
                  resolve()
                },
                oError => {
                  reject(oError)
                }
              )
          } else {
            reject(_oUnKnownError)
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_GoPreset = function (iPresetID, options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd, iPresetID: iPresetID }
        m_utilsInc.extend(newOptions, options)
        let iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          iIndex = this.findDeviceIndexByIP(wndInfo.szIP)
          if (iIndex != -1) {
            const oDeviceInfo = m_deviceSet[iIndex]
            oDeviceInfo.oProtocolInc
              .goPreset(oDeviceInfo, wndInfo, newOptions)
              .then(
                () => {
                  resolve()
                },
                oError => {
                  reject(oError)
                }
              )
          } else {
            reject(_oUnKnownError)
          }
        } else {
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_RecordSearch = function (
      szDeviceIdentify,
      iChannelID,
      szStartTime,
      szEndTime,
      options
    ) {
      const oPromise = new Promise(async function (resolve, reject) {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          if (
            oDeviceInfo.szDeviceType === DEVICE_TYPE_IPCAMERA ||
            oDeviceInfo.szDeviceType === DEVICE_TYPE_IPDOME ||
            oDeviceInfo.szDeviceType === DEVICE_TYPE_IPZOOM
          ) {
            szStartTime = m_utilsInc.convertToUTCTime(szStartTime)
            szEndTime = m_utilsInc.convertToUTCTime(szEndTime)
          }
          const newOptions = {
            iChannelID: iChannelID,
            szStartTime: szStartTime,
            szEndTime: szEndTime,
            iSearchPos: 0,
            iStreamType: 1
          }
          m_utilsInc.extend(newOptions, options)
          newOptions.success = null
          oDeviceInfo.oProtocolInc.recordSearch(oDeviceInfo, newOptions).then(
            oData => {
              if (
                oDeviceInfo.szDeviceType === DEVICE_TYPE_IPCAMERA ||
                oDeviceInfo.szDeviceType === DEVICE_TYPE_IPDOME ||
                oDeviceInfo.szDeviceType === DEVICE_TYPE_IPZOOM
              ) {
                let szRecordStartTime = ''
                let szRecordEndTime = ''
                for (
                  let i = 0, nLen = $(oData).find('searchMatchItem').length;
                  i < nLen;
                  i++
                ) {
                  szRecordStartTime = $(oData)
                    .find('startTime')
                    .eq(i)
                    .text()
                  szRecordEndTime = $(oData)
                    .find('endTime')
                    .eq(i)
                    .text()
                  szRecordStartTime = m_utilsInc.convertToLocalTime(
                    szRecordStartTime,
                    oDeviceInfo.iDeviceMinusLocalTime
                  )
                  szRecordEndTime = m_utilsInc.convertToLocalTime(
                    szRecordEndTime,
                    oDeviceInfo.iDeviceMinusLocalTime
                  )
                  $(oData)
                    .find('startTime')
                    .eq(i)
                    .text(szRecordStartTime)
                  $(oData)
                    .find('endTime')
                    .eq(i)
                    .text(szRecordEndTime)
                }
              }
              if (options.success) {
                options.success(oData)
              }
              resolve(oData)
            },
            oError => {
              reject(oError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_StartPlayback = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        let iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        let cgi = ''
        let urlProtocol = ''
        let iChannelID = 1
        let iStream = 0
        const szCurTime = m_utilsInc.dateFormat(new Date(), 'yyyy-MM-dd')
        const newOptions = {
          iWndIndex: m_iSelWnd,
          iStreamType: 1,
          iChannelID: 1,
          szStartTime: szCurTime + ' 00:00:00',
          szEndTime: szCurTime + ' 23:59:59'
        }
        m_utilsInc.extend(newOptions, options)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          cgi = oDeviceInfo.oProtocolInc.CGI.startPlayback
          urlProtocol = 'rtsp://'
          iStream = newOptions.iStreamType
          iChannelID = newOptions.iChannelID * 100 + iStream
          m_utilsInc.extend(newOptions, {
            urlProtocol: urlProtocol,
            cgi: cgi,
            iChannelID: iChannelID
          })
          iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
          if (iIndex == -1) {
            if (
              oDeviceInfo.szDeviceType === DEVICE_TYPE_IPCAMERA ||
              oDeviceInfo.szDeviceType === DEVICE_TYPE_IPDOME ||
              oDeviceInfo.szDeviceType === DEVICE_TYPE_IPZOOM
            ) {
              newOptions.szStartTime = m_utilsInc.convertToUTCTime(
                newOptions.szStartTime
              )
              newOptions.szEndTime = m_utilsInc.convertToUTCTime(
                newOptions.szEndTime
              )
            }
            newOptions.szStartTime =
              newOptions.szStartTime.replace(/[-:]/g, '').replace(' ', 'T') +
              'Z'
            newOptions.szEndTime =
              newOptions.szEndTime.replace(/[-:]/g, '').replace(' ', 'T') + 'Z'
            oDeviceInfo.oProtocolInc
              .startPlayback(oDeviceInfo, newOptions)
              .then(
                function () {
                  if (options.success) {
                    options.success()
                  }
                  resolve()
                },
                function () {
                  if (options.error) {
                    options.error(_oUnKnownError)
                  }
                  reject(_oUnKnownError)
                }
              )
          }
        } else {
          if (options.error) {
            options.error(_oNoLoginError)
          }
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_ReversePlayback = function (szDeviceIdentify, options) {
      const oPromise = new Promise(function (resolve, reject) {
        let iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        const iRet = -1
        let cgi = ''
        let urlProtocol = ''
        const iPort = -1
        let iChannelID = -1
        let iStream = 0
        const szCurTime = m_utilsInc.dateFormat(new Date(), 'yyyy-MM-dd')
        const newOptions = {
          iWndIndex: m_iSelWnd,
          iStreamType: 1,
          iChannelID: 1,
          szStartTime: szCurTime + ' 00:00:00',
          szEndTime: szCurTime + ' 23:59:59'
        }
        m_utilsInc.extend(newOptions, options)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          const iProtocolType = parseInt(m_oLocalCfg.protocolType, 10)
          cgi = oDeviceInfo.oProtocolInc.CGI.startPlayback
          urlProtocol = 'rtsp://'
          iStream = newOptions.iStreamType
          iChannelID = newOptions.iChannelID * 100 + iStream
          m_utilsInc.extend(newOptions, {
            urlProtocol: urlProtocol,
            cgi: cgi,
            iChannelID: iChannelID
          })
          iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
          if (iIndex == -1) {
            newOptions.szStartTime =
              newOptions.szStartTime.replace(/[-:]/g, '').replace(' ', 'T') +
              'Z'
            newOptions.szEndTime =
              newOptions.szEndTime.replace(/[-:]/g, '').replace(' ', 'T') + 'Z'
            oDeviceInfo.oProtocolInc
              .reversePlayback(oDeviceInfo, newOptions)
              .then(
                function () {
                  if (options.success) {
                    options.success()
                  }
                  resolve()
                },
                function () {
                  if (options.error) {
                    options.error(_oUnKnownError)
                  }
                  reject(_oUnKnownError)
                }
              )
          }
        }
      })
      return oPromise
    }
    this.I_Frame = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          const iPlayStatus = wndInfo.iPlayStatus
          if (
            iPlayStatus == PLAY_STATUS_PLAYBACK ||
            iPlayStatus == PLAY_STATUS_FRAME
          ) {
            m_pluginOBJECT.JS_FrameForward(newOptions.iWndIndex).then(
              function () {
                wndInfo.iPlayStatus = PLAY_STATUS_FRAME
                if (newOptions.success) {
                  newOptions.success()
                }
                resolve()
              },
              function () {
                if (newOptions.error) {
                  newOptions.error(_oUnKnownError)
                }
                reject(_oUnKnownError)
              }
            )
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
          }
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_Pause = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          const iPlayStatus = wndInfo.iPlayStatus
          let iNextStatus = -1
          if (iPlayStatus == PLAY_STATUS_PLAYBACK) {
            iNextStatus = PLAY_STATUS_PAUSE
          } else if (iPlayStatus == PLAY_STATUS_REVERSE_PLAYBACK) {
            iNextStatus = PLAY_STATUS_REVERSE_PAUSE
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
            return
          }
          m_pluginOBJECT.JS_Pause(newOptions.iWndIndex).then(
            function () {
              wndInfo.iPlayStatus = iNextStatus
              if (newOptions.success) {
                newOptions.success()
              }
              resolve()
            },
            function () {
              if (newOptions.error) {
                newOptions.error(_oUnKnownError)
              }
              reject(_oUnKnownError)
            }
          )
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_Resume = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          const iPlayStatus = wndInfo.iPlayStatus
          let iNextStatus = -1
          if (
            iPlayStatus == PLAY_STATUS_PAUSE ||
            iPlayStatus == PLAY_STATUS_FRAME
          ) {
            iNextStatus = PLAY_STATUS_PLAYBACK
          } else if (iPlayStatus == PLAY_STATUS_REVERSE_PAUSE) {
            iNextStatus = PLAY_STATUS_REVERSE_PLAYBACK
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
            return
          }
          m_pluginOBJECT.JS_Resume(newOptions.iWndIndex).then(
            function () {
              wndInfo.iPlayStatus = iNextStatus
              if (newOptions.success) {
                newOptions.success()
              }
              resolve()
            },
            function () {
              if (newOptions.error) {
                newOptions.error(_oUnKnownError)
              }
              reject(_oUnKnownError)
            }
          )
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_PlaySlow = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.iPlayStatus == PLAY_STATUS_PLAYBACK) {
            m_pluginOBJECT.JS_Slow(newOptions.iWndIndex).then(
              function () {
                if (newOptions.success) {
                  newOptions.success()
                }
                resolve()
              },
              function () {
                if (newOptions.error) {
                  newOptions.error(_oUnKnownError)
                }
                reject(_oUnKnownError)
              }
            )
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
          }
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_PlayFast = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          const wndInfo = m_wndSet[iIndex]
          if (wndInfo.iPlayStatus == PLAY_STATUS_PLAYBACK) {
            m_pluginOBJECT.JS_Fast(newOptions.iWndIndex).then(
              function () {
                if (newOptions.success) {
                  newOptions.success()
                }
                resolve()
              },
              function () {
                if (newOptions.error) {
                  newOptions.error(_oUnKnownError)
                }
                reject(_oUnKnownError)
              }
            )
          } else {
            if (newOptions.error) {
              newOptions.error(_oUnKnownError)
            }
            reject(_oUnKnownError)
          }
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_GetOSDTime = function (options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const newOptions = { iWndIndex: m_iSelWnd }
        if (m_utilsInc.isObject(options)) {
          m_utilsInc.extend(newOptions, options)
        } else {
          if (!m_utilsInc.isUndefined(options)) {
            newOptions.iWndIndex = options
          }
        }
        const iIndex = this.findWndIndexByIndex(newOptions.iWndIndex)
        if (iIndex != -1) {
          m_pluginOBJECT.JS_GetOSDTime(newOptions.iWndIndex).then(
            function (iTime) {
              if (newOptions.success) {
                var szOSDTime = m_utilsInc.dateFormat(
                  new Date(iTime * 1e3),
                  'yyyy-MM-dd hh:mm:ss'
                )
                newOptions.success(szOSDTime)
              }
              resolve(szOSDTime)
            },
            function () {
              if (newOptions.error) {
                newOptions.error(_oUnKnownError)
              }
              reject(_oUnKnownError)
            }
          )
        } else {
          if (newOptions.error) {
            newOptions.error(_oUnKnownError)
          }
          reject(_oUnKnownError)
        }
      })
      return oPromise
    }
    this.I_StartDownloadRecord = function (
      szDeviceIdentify,
      szPlaybackURI,
      szFileName,
      options
    ) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          const newOptions = {
            szPlaybackURI: szPlaybackURI,
            szFileName: szFileName + '.mp4',
            bDateDir: true
          }
          if (!m_utilsInc.isUndefined(options)) {
            m_utilsInc.extend(newOptions, options)
          }
          oDeviceInfo.oProtocolInc
            .startDownloadRecord(oDeviceInfo, newOptions)
            .then(
              iDownloadID => {
                resolve(iDownloadID)
              },
              oError => {
                reject(oError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_StartDownloadRecordByTime = function (
      szDeviceIdentify,
      szPlaybackURI,
      szFileName,
      szStartTime,
      szEndTime,
      options
    ) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          szPlaybackURI =
            szPlaybackURI.split('?')[0] +
            '?starttime=' +
            szStartTime.replace(' ', 'T') +
            'Z&endtime=' +
            szEndTime.replace(' ', 'T') +
            'Z'
          const newOptions = {
            szPlaybackURI: szPlaybackURI,
            szFileName: szFileName + '.mp4',
            bDateDir: true
          }
          if (!m_utilsInc.isUndefined(options)) {
            m_utilsInc.extend(newOptions, options)
          }
          oDeviceInfo.oProtocolInc
            .startDownloadRecord(oDeviceInfo, newOptions)
            .then(
              iDownloadID => {
                resolve(iDownloadID)
              },
              oError => {
                reject(oError)
              }
            )
        }
      })
      return oPromise
    }
    this.I_GetDownloadStatus = function (iDownloadID) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_GetDownloadStatus(iDownloadID).then(
          data => {
            resolve(data)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetDownloadProgress = function (iDownloadID) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_GetDownloadProgress(iDownloadID).then(
          data => {
            resolve(data)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_StopDownloadRecord = function (iDownloadID) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_StopAsyncDownload(iDownloadID).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_ExportDeviceConfig = function (szDeviceIdentify) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc.exportDeviceConfig(oDeviceInfo).then(
            () => {
              resolve()
            },
            () => {
              reject(_oUnKnownError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_ImportDeviceConfig = function (szDeviceIdentify, szFileName) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          const newOptions = { szFileName: szFileName }
          oDeviceInfo.oProtocolInc
            .importDeviceConfig(oDeviceInfo, newOptions)
            .then(
              () => {
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_RestoreDefault = function (szDeviceIdentify, szMode, options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { success: null, error: null }
        m_utilsInc.extend(newOptions, options)
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc
            .restore(oDeviceInfo, szMode, newOptions)
            .then(
              () => {
                resolve()
              },
              oError => {
                reject(oError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_Restart = function (szDeviceIdentify, options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { success: null, error: null }
        m_utilsInc.extend(newOptions, options)
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc.restart(oDeviceInfo, newOptions).then(
            () => {
              resolve()
            },
            oError => {
              reject(oError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_Reconnect = function (szDeviceIdentify, options) {
      const oPromise = new Promise((resolve, reject) => {
        const newOptions = { success: null, error: null }
        m_utilsInc.extend(newOptions, options)
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc
            .login(
              oDeviceInfo.szIP,
              oDeviceInfo.iCGIPort,
              oDeviceInfo.szAuth,
              newOptions
            )
            .then(
              () => {
                resolve()
              },
              oError => {
                reject(oError)
              }
            )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_StartUpgrade = function (szDeviceIdentify, szFileName) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          oDeviceInfo.oProtocolInc.startUpgrade(oDeviceInfo, szFileName).then(
            () => {
              resolve()
            },
            () => {
              reject(_oUnKnownError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_UpgradeStatus = function (szDeviceIdentify) {
      const oPromise = new Promise((resolve, reject) => {
        this.I_SendHTTPRequest(
          szDeviceIdentify,
          m_ISAPIProtocol.CGI.startUpgrade.status,
          {}
        ).then(
          data => {
            const bUpgrading =
              $(data)
                .find('upgrading')
                .eq(0)
                .text() === 'true'
            resolve(bUpgrading)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_UpgradeProgress = function (szDeviceIdentify) {
      const oPromise = new Promise((resolve, reject) => {
        this.I_SendHTTPRequest(
          szDeviceIdentify,
          m_ISAPIProtocol.CGI.startUpgrade.status,
          {}
        ).then(
          data => {
            const iPercent = parseInt(
              $(data)
                .find('percent')
                .eq(0)
                .text(),
              10
            )
            resolve(iPercent)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_StopUpgrade = function () {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_StopUpgrade().then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_CheckPluginInstall = function () {
      return true
    }
    this.I_CheckPluginVersion = function () {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_CheckUpdate(m_szVersion).then(
          bFlag => {
            resolve(bFlag)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_SendHTTPRequest = function (szDeviceIdentify, szURI, options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const httpClient = new HttpPluginClient()
        let szURL = ''
        let szAuth = ''
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex >= 0) {
          if (szURI.substr(0, 1) === '%') {
            szURI = szURI.substr(8)
          }
          const oDeviceInfo = m_deviceSet[iIndex]
          szURL =
            oDeviceInfo.szHttpProtocol +
            oDeviceInfo.szIP +
            ':' +
            oDeviceInfo.iCGIPort +
            '/' +
            szURI
          szAuth = oDeviceInfo.szAuth
        }
        const newOptions = {
          type: 'GET',
          url: szURL,
          auth: szAuth,
          success: null,
          error: null
        }
        m_utilsInc.extend(newOptions, options)
        httpClient.submitRequest(newOptions).then(
          function (oRes) {
            if (oRes.httpStatusCode === 200) {
              let oData
              if (oRes.httpResponse.indexOf('<?xml') === 0) {
                oData = m_utilsInc.loadXML(oRes.httpResponse)
              } else {
                oData = JSON.parse(oRes.httpResponse)
              }
              options.success && options.success(oData)
              resolve(oData)
            } else if (oRes.httpStatusCode !== 200) {
              let oData = m_utilsInc.loadXML(oRes.httpResponse)
              if (!oData) {
                oData = JSON.parse(oRes.httpResponse)
              }
              options.error &&
                options.error({
                  errorCode: oRes.httpStatusCode,
                  errorMsg: oData
                })
              reject({ errorCode: oRes.httpStatusCode, errorMsg: oData })
            }
          },
          function (errorCode) {
            if (options.error) {
              options.error({ errorCode: errorCode, errorMsg: '' })
            }
            reject({ errorCode: errorCode, errorMsg: '' })
          }
        )
      })
      return oPromise
    }
    this.I_ChangeWndNum = function (iWndType) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_ArrangeWindow(iWndType).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetLastError = function () {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_GetLastError().then(
          data => {
            resolve(data)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetWindowStatus = function (iWndIndex) {
      if (m_utilsInc.isUndefined(iWndIndex)) {
        var wndSet = []
        m_utilsInc.extend(wndSet, m_wndSet)
        return wndSet
      } else {
        const i = this.findWndIndexByIndex(iWndIndex)
        if (i != -1) {
          var wndSet = {}
          m_utilsInc.extend(wndSet, m_wndSet[i])
          return wndSet
        } else {
          return null
        }
      }
    }
    this.I_GetIPInfoByMode = function (iMode, szAddress, iPort, szDeviceInfo) {}
    this.I_SetPlayModeType = function (iMode) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_SetPlayMode(iMode).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_SetSnapDrawMode = function (iWndIndex, iMode) {
      let bType = false
      if (iMode !== -1) {
        bType = true
      }
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_SetDrawStatus(bType, iMode).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_SetSnapPolygonInfo = function (iWndIndex, szInfo) {
      const oPromise = new Promise((resolve, reject) => {
        const aP = []
        const oData = m_utilsInc.formatPolygonXmlToJson(szInfo)
        if (oData.aAddRect.length > 0) {
          aP.push(
            m_pluginOBJECT.JS_SetDrawShapeInfo('Rect', oData.aAddRect[0])
          )
        }
        if (oData.aAddPolygon.length > 0) {
          aP.push(
            m_pluginOBJECT.JS_SetDrawShapeInfo('Polygon', oData.aAddPolygon[0])
          )
        }
        if (oData.aRect.length > 0) {
          aP.push(m_pluginOBJECT.JS_SetRectInfo(oData.aRect))
        }
        if (oData.aPolygon.length > 0) {
          aP.push(m_pluginOBJECT.JS_SetPolygonInfo(oData.aPolygon))
        }
        Promise.all(aP).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetSnapPolygonInfo = function (iWndIndex) {
      const oPromise = new Promise((resolve, reject) => {
        const aP = []
        aP.push(m_pluginOBJECT.JS_GetPolygonInfo())
        aP.push(m_pluginOBJECT.JS_GetRectInfo())
        Promise.all(aP).then(
          aData => {
            const szXmlData = m_utilsInc.formatPolygonJsonToXml(aData)
            resolve(szXmlData)
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_ClearSnapInfo = function (iWndIndex, aShapes) {
      const oPromise = new Promise((resolve, reject) => {
        if (aShapes) {
          const aPolygon = []
          const aRect = []
          aShapes.forEach(item => {
            if (item.polygonType === 1) {
              aPolygon.push(item.id)
            } else {
              aRect.push(item.id)
            }
            const aP = []
            if (aPolygon.length) {
              aP.push(m_pluginOBJECT.JS_ClearShapeByType('Polygon', aPolygon))
            }
            if (aRect.length) {
              aP.push(m_pluginOBJECT.JS_ClearShapeByType('Rect', aRect))
            }
            Promise.all(aP).then(
              () => {
                resolve()
              },
              () => {
                reject(_oUnKnownError)
              }
            )
          })
        } else {
          m_pluginOBJECT.JS_ClearShapeByType('AllWindows').then(
            () => {
              resolve()
            },
            () => {
              reject(_oUnKnownError)
            }
          )
        }
      })
      return oPromise
    }
    this.I_DeviceCapturePic = function (
      szDeviceIdentify,
      iChannelID,
      szPicName,
      options
    ) {
      return false
      const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
      let iRet = -1
      if (iIndex != -1) {
        const oDeviceInfo = m_deviceSet[iIndex]
        const newOptions = { bDateDir: true }
        m_utilsInc.extend(newOptions, options)
        if (
          !m_utilsInc.isUndefined(newOptions.iResolutionWidth) &&
          !m_utilsInc.isInt(newOptions.iResolutionWidth)
        ) {
          return iRet
        }
        if (
          !m_utilsInc.isUndefined(newOptions.iResolutionHeight) &&
          !m_utilsInc.isInt(newOptions.iResolutionHeight)
        ) {
          return iRet
        }
        iRet = oDeviceInfo.oProtocolInc.deviceCapturePic(
          oDeviceInfo,
          iChannelID,
          szPicName,
          newOptions
        )
      }
      return iRet
    }
    this.I_SetPackageType = function (iPackageType) {
      const oPromise = new Promise((resolve, reject) => {
        m_pluginOBJECT.JS_SetPackageType(iPackageType).then(
          () => {
            resolve()
          },
          () => {
            reject(_oUnKnownError)
          }
        )
      })
      return oPromise
    }
    this.I_GetDevicePort = function (szDeviceIdentify) {
      const oPromise = new Promise(async (resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        let oPort = null
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          try {
            oPort = await _getPort(oDeviceInfo)
            resolve(oPort)
          } catch (err) {
            reject({ errorCode: ERROR_CODE_NETWORKERROR, errorMsg: '' })
          }
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.I_GetTextOverlay = function (szUrl, szDeviceIdentify, options) {
      const oPromise = new Promise((resolve, reject) => {
        const iIndex = this.findDeviceIndexByIP(szDeviceIdentify)
        if (iIndex != -1) {
          const oDeviceInfo = m_deviceSet[iIndex]
          const wndInfo = m_wndSet[iIndex]
          const newOptions = {
            type: 'GET',
            success: options.success,
            error: options.error
          }
          this.I_SendHTTPRequest(
            oDeviceInfo.szIP + '_' + oDeviceInfo.iCGIPort,
            szUrl,
            newOptions
          ).then(
            oData => {
              resolve(oData)
            },
            oError => {
              reject(oError)
            }
          )
        } else {
          reject(_oNoLoginError)
        }
      })
      return oPromise
    }
    this.findDeviceIndexByIP = function (szDeviceIdentify) {
      if (szDeviceIdentify.indexOf('_') > -1) {
        for (var i = 0, iLen = m_deviceSet.length; i < iLen; i++) {
          if (m_deviceSet[i].szDeviceIdentify == szDeviceIdentify) {
            return i
          }
        }
      } else {
        for (var i = 0, iLen = m_deviceSet.length; i < iLen; i++) {
          if (m_deviceSet[i].szIP == szDeviceIdentify) {
            return i
          }
        }
      }
      return -1
    }
    this.findWndIndexByIndex = function (iWndIndex) {
      for (let i = 0, iLen = m_wndSet.length; i < iLen; i++) {
        if (m_wndSet[i].iIndex == iWndIndex) {
          return i
        }
      }
      return -1
    }
    function deviceInfoClass () {
      this.szIP = ''
      this.szHostName = ''
      this.szAuth = ''
      this.szHttpProtocol = 'http://'
      this.iCGIPort = 80
      this.szDeviceIdentify = ''
      this.iDevicePort = -1
      this.iHttpPort = -1
      this.iHttpsPort = -1
      this.iRtspPort = -1
      this.iAudioType = 1
      this.m_iAudioBitRate = -1
      this.m_iAudioSamplingRate = -1
      this.iDeviceProtocol = PROTOCOL_DEVICE_ISAPI
      this.oProtocolInc = null
      this.iAnalogChannelNum = 0
      this.szDeviceType = ''
      this.bVoiceTalk = false
      this.iDeviceMinusLocalTime = 0
    }
    const wndInfoClass = function () {
      this.iIndex = 0
      this.szIP = ''
      this.iCGIPort = 80
      this.szDeviceIdentify = ''
      this.iChannelID = ''
      this.iPlayStatus = PLAY_STATUS_STOP
      this.bSound = false
      this.bRecord = false
      this.bPTZAuto = false
      this.bEZoom = false
      this.b3DZoom = false
    }
    var HttpPluginClient = function () {
      this.options = {
        type: 'GET',
        url: '',
        auth: '',
        timeout: 3e4,
        data: '',
        async: true,
        success: null,
        error: null
      }
      this.m_szHttpHead = ''
      this.m_szHttpContent = ''
      this.m_szHttpData = ''
    }
    HttpPluginClient.prototype.submitRequest = function (options) {
      options.method = this.getHttpMethod(options.type)
      options.content = options.data
      delete options.type
      delete options.data
      return m_pluginOBJECT.JS_SubmitHttpRequest(options)
    }
    HttpPluginClient.prototype.getHttpMethod = function (szMethod) {
      const oMethod = { GET: 1, POST: 2, PUT: 5, DELETE: 6 }
      const iMethod = oMethod[szMethod]
      return iMethod || -1
    }
    const ISAPIProtocol = function () {}
    ISAPIProtocol.prototype.CGI = {
      login: '%s%s:%s/ISAPI/Security/userCheck?format=json',
      getAudioInfo: '%s%s:%s/ISAPI/System/TwoWayAudio/channels',
      getDeviceInfo: '%s%s:%s/ISAPI/System/deviceInfo',
      getAnalogChannelInfo: '%s%s:%s/ISAPI/System/Video/inputs/channels',
      getDigitalChannel: '%s%s:%s/ISAPI/ContentMgmt/InputProxy/channels',
      getDigitalChannelInfo:
        '%s%s:%s/ISAPI/ContentMgmt/InputProxy/channels/status',
      getZeroChannelInfo: '%s%s:%s/ISAPI/ContentMgmt/ZeroVideo/channels',
      getStreamChannels: {
        analog: '%s%s:%s/ISAPI/Streaming/channels',
        digital: '%s%s:%s/ISAPI/ContentMgmt/StreamingProxy/channels'
      },
      startRealPlay: {
        channels: 'video://%s:%s/%s',
        zeroChannels:
          '%s%s:%s/PSIA/Custom/SelfExt/ContentMgmt/ZeroStreaming/channels/%s'
      },
      startVoiceTalk: {
        open: '%s%s:%s/ISAPI/System/TwoWayAudio/channels/%s/open',
        close: '%s%s:%s/ISAPI/System/TwoWayAudio/channels/%s/close',
        audioData: '%s%s:%s/ISAPI/System/TwoWayAudio/channels/%s/audioData'
      },
      ptzControl: {
        analog: '%s%s:%s/ISAPI/PTZCtrl/channels/%s/continuous',
        digital: '%s%s:%s/ISAPI/ContentMgmt/PTZCtrlProxy/channels/%s/continuous'
      },
      ptzAutoControl: {
        analog: '%s%s:%s/ISAPI/PTZCtrl/channels/%s/autoPan',
        digital: '%s%s:%s/ISAPI/ContentMgmt/PTZCtrlProxy/channels/%s/autoPan'
      },
      setPreset: {
        analog: '%s%s:%s/ISAPI/PTZCtrl/channels/%s/presets/%s',
        digital: '%s%s:%s/ISAPI/ContentMgmt/PTZCtrlProxy/channels/%s/presets/%s'
      },
      goPreset: {
        analog: '%s%s:%s/ISAPI/PTZCtrl/channels/%s/presets/%s/goto',
        digital:
          '%s%s:%s/ISAPI/ContentMgmt/PTZCtrlProxy/channels/%s/presets/%s/goto'
      },
      ptzFocus: {
        analog: '%s%s:%s/ISAPI/System/Video/inputs/channels/%s/focus',
        digital: '%s%s:%s/ISAPI/ContentMgmt/ImageProxy/channels/%s/focus'
      },
      ptzIris: {
        analog: '%s%s:%s/ISAPI/System/Video/inputs/channels/%s/iris',
        digital: '%s%s:%s/ISAPI/ContentMgmt/ImageProxy/channels/%s/iris'
      },
      getNetworkBond: '%s%s:%s/ISAPI/System/Network/Bond',
      getNetworkInterface: '%s%s:%s/ISAPI/System/Network/interfaces',
      getUPnPPortStatus: '%s%s:%s/ISAPI/System/Network/UPnP/ports/status',
      getPPPoEStatus: '%s%s:%s/ISAPI/System/Network/PPPoE/1/status',
      getPortInfo: '%s%s:%s/ISAPI/Security/adminAccesses',
      recordSearch: '%s%s:%s/ISAPI/ContentMgmt/search',
      startPlayback: 'video://%s:%s/%s',
      startWsPlayback: '%s%s:%s/%s',
      startShttpPlayback: '%s%s:%s/SDK/playback/%s',
      startShttpReversePlayback: '%s%s:%s/SDK/playback/%s/reversePlay',
      startTransCodePlayback: '%s%s:%s/SDK/playback/%s/transcoding',
      startDownloadRecord: '%s%s:%s/ISAPI/ContentMgmt/download',
      downloaddeviceConfig: '%s%s:%s/ISAPI/System/configurationData',
      uploaddeviceConfig: '%s%s:%s/ISAPI/System/configurationData',
      restart: '%s%s:%s/ISAPI/System/reboot',
      restore: '%s%s:%s/ISAPI/System/factoryReset?mode=%s',
      startUpgrade: {
        upgrade: '%s%s:%s/ISAPI/System/updateFirmware',
        status: '%s%s:%s/ISAPI/System/upgradeStatus'
      },
      set3DZoom: {
        analog: '%s%s:%s/ISAPI/PTZCtrl/channels/%s/position3D',
        digital: '%s%s:%s/ISAPI/ContentMgmt/PTZCtrlProxy/channels/%s/position3D'
      },
      getSecurityVersion: '%s%s:%s/ISAPI/Security/capabilities?username=admin',
      SDKCapabilities: '%s%s:%s/SDK/capabilities',
      deviceCapture: {
        channels: '%s%s:%s/ISAPI/Streaming/channels/%s/picture'
      },
      overlayInfo: {
        analog: '%s%s:%s/ISAPI/System/Video/inputs/channels/%s/overlays/',
        digital:
          '%s%s:%s/ISAPI/ContentMgmt/InputProxy/channels/%s/video/overlays'
      },
      sessionCap:
        '%s%s:%s/ISAPI/Security/sessionLogin/capabilities?username=%s',
      sessionLogin: '%s%s:%s/ISAPI/Security/sessionLogin',
      sessionHeartbeat: '%s%s:%s/ISAPI/Security/sessionHeartbeat',
      sessionLogout: '%s%s:%s/ISAPI/Security/sessionLogout',
      systemCapabilities: '%s%s:%s/ISAPI/System/capabilities',
      time: 'ISAPI/System/time'
    }
    ISAPIProtocol.prototype.login = function (szIP, iPort, szAuth, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.login,
        options
      )
    }
    ISAPIProtocol.prototype.getAudioInfo = function (oDeviceInfo, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const newOptions = {}
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function (oData) {
            const oNodeList = NS.$XML(oData).find('audioCompressionType', true)
            if (oNodeList.length > 0) {
              const szAudioCompressionType = NS.$XML(oNodeList)
                .eq(0)
                .text()
              let iAudioType = 0
              if (szAudioCompressionType == 'G.711ulaw') {
                iAudioType = 1
              } else if (szAudioCompressionType == 'G.711alaw') {
                iAudioType = 2
              } else if (szAudioCompressionType == 'G.726') {
                iAudioType = 3
              } else if (
                szAudioCompressionType == 'MP2L2' ||
                szAudioCompressionType == 'MPEL2'
              ) {
                iAudioType = 4
              } else if (szAudioCompressionType == 'G.722.1') {
                iAudioType = 0
              } else if (szAudioCompressionType == 'AAC') {
                iAudioType = 5
              } else if (szAudioCompressionType == 'PCM') {
                iAudioType = 6
              } else if (szAudioCompressionType == 'MP3') {
                iAudioType = 7
              }
              oDeviceInfo.iAudioType = iAudioType
            }
            if (
              NS.$XML(oData)
                .find('audioBitRate')
                .eq(0)
                .text() !== ''
            ) {
              oDeviceInfo.m_iAudioBitRate =
                parseInt(
                  NS.$XML(oData)
                    .find('audioBitRate')
                    .eq(0)
                    .text(),
                  10
                ) * 1e3
            } else {
              oDeviceInfo.m_iAudioBitRate = 0
            }
            if (
              NS.$XML(oData)
                .find('audioSamplingRate')
                .eq(0)
                .text() !== ''
            ) {
              oDeviceInfo.m_iAudioSamplingRate =
                parseInt(
                  NS.$XML(oData)
                    .find('audioSamplingRate')
                    .eq(0)
                    .text(),
                  10
                ) * 1e3
            } else {
              oDeviceInfo.m_iAudioSamplingRate = 0
            }
            if (
              NS.$XML(oData)
                .find('channelNum')
                .eq(0)
                .text() !== ''
            ) {
              oDeviceInfo.m_iSoundChan = parseInt(
                NS.$XML(oData)
                  .find('channelNum')
                  .eq(0)
                  .text(),
                10
              )
            } else {
              oDeviceInfo.m_iSoundChan = 1
            }
            if (
              NS.$XML(oData)
                .find('deviceCastChannelNum')
                .eq(0)
                .text() !== ''
            ) {
              oDeviceInfo.m_iDeviceAudioSoundChan = parseInt(
                NS.$XML(oData)
                  .find('deviceCastChannelNum')
                  .eq(0)
                  .text(),
                10
              )
            } else {
              oDeviceInfo.m_iDeviceAudioSoundChan = 1
            }
            if (options.success) {
              options.success(oData)
            }
            resolve(oData)
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          m_ISAPIProtocol.CGI.getAudioInfo,
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.getDeviceInfo = function (oDeviceInfo, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const newOptions = {}
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function (xmlDoc) {
            let oData
            oDeviceInfo.szDeviceType = NS.$XML(xmlDoc)
              .find('deviceType')
              .eq(0)
              .text()
            const arrXml = []
            arrXml.push('<DeviceInfo>')
            arrXml.push(
              '<deviceName>' +
                m_utilsInc.escape(
                  NS.$XML(xmlDoc)
                    .find('deviceName')
                    .eq(0)
                    .text()
                ) +
                '</deviceName>'
            )
            arrXml.push(
              '<deviceID>' +
                NS.$XML(xmlDoc)
                  .find('deviceID')
                  .eq(0)
                  .text() +
                '</deviceID>'
            )
            arrXml.push(
              '<deviceType>' +
                NS.$XML(xmlDoc)
                  .find('deviceType')
                  .eq(0)
                  .text() +
                '</deviceType>'
            )
            arrXml.push(
              '<model>' +
                NS.$XML(xmlDoc)
                  .find('model')
                  .eq(0)
                  .text() +
                '</model>'
            )
            arrXml.push(
              '<serialNumber>' +
                NS.$XML(xmlDoc)
                  .find('serialNumber')
                  .eq(0)
                  .text() +
                '</serialNumber>'
            )
            arrXml.push(
              '<macAddress>' +
                NS.$XML(xmlDoc)
                  .find('macAddress')
                  .eq(0)
                  .text() +
                '</macAddress>'
            )
            arrXml.push(
              '<firmwareVersion>' +
                NS.$XML(xmlDoc)
                  .find('firmwareVersion')
                  .eq(0)
                  .text() +
                '</firmwareVersion>'
            )
            arrXml.push(
              '<firmwareReleasedDate>' +
                NS.$XML(xmlDoc)
                  .find('firmwareReleasedDate')
                  .eq(0)
                  .text() +
                '</firmwareReleasedDate>'
            )
            arrXml.push(
              '<encoderVersion>' +
                NS.$XML(xmlDoc)
                  .find('encoderVersion')
                  .eq(0)
                  .text() +
                '</encoderVersion>'
            )
            arrXml.push(
              '<encoderReleasedDate>' +
                NS.$XML(xmlDoc)
                  .find('encoderReleasedDate')
                  .eq(0)
                  .text() +
                '</encoderReleasedDate>'
            )
            arrXml.push('</DeviceInfo>')
            oData = m_utilsInc.loadXML(arrXml.join(''))
            if (options.success) {
              options.success(oData)
            }
            resolve(oData)
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          m_ISAPIProtocol.CGI.getDeviceInfo,
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.getDeviceMinusLocalTime = function (oDeviceInfo) {
      const oPromise = new Promise(function (resolve, reject) {
        const newOptions = {
          success: xmlDoc => {
            const szDeviceTime = $(xmlDoc)
              .find('localTime')
              .eq(0)
              .text()
              .substring(0, 19)
            const arDTms = szDeviceTime.match(
              /(\d+)-(\d+)-(\d+)(\D+)(\d+):(\d+):(\d+)/
            )
            if (arDTms.length !== 8) {
              return
            }
            const dtDeviceDate = new Date(
              arDTms[1],
              arDTms[2] - 1,
              arDTms[3],
              arDTms[5],
              arDTms[6],
              arDTms[7]
            )
            const szTimeZone = $(xmlDoc)
              .find('timeZone')
              .eq(0)
              .text()
            let iDSTTime = 0
            const iDSTPos = szTimeZone.indexOf('DST')
            if (iDSTPos != -1) {
              const dtDSTStart = new Date(dtDeviceDate.getTime())
              dtDSTStart.setMinutes(0)
              dtDSTStart.setSeconds(0)
              const dtDSTStop = new Date(dtDeviceDate.getTime())
              dtDSTStop.setMinutes(0)
              dtDSTStop.setSeconds(0)
              const szDSTStartTime = szTimeZone.split(',')[1]
              const szDSTStopTime = szTimeZone.split(',')[2]
              const iDSTStartMonth = parseInt(
                szDSTStartTime.split('.')[0].replace('M', ''),
                10
              )
              dtDSTStart.setMonth(iDSTStartMonth - 1)
              const iDSTStartWeek = parseInt(szDSTStartTime.split('.')[1], 10)
              const iDSTStartDay = parseInt(
                szDSTStartTime.split('.')[2].split('/')[0]
              )
              const iDSTStartTime = parseInt(
                szDSTStartTime
                  .split('.')[2]
                  .split('/')[1]
                  .split(':')[0],
                10
              )
              dtDSTStart.setHours(iDSTStartTime)
              let iTime = 0
              let iDate = 0
              for (var i = 1; i <= 31; i++) {
                dtDSTStart.setDate(i)
                if (dtDSTStart.getMonth() !== iDSTStartMonth - 1) {
                  break
                }
                if (dtDSTStart.getDay() == iDSTStartDay) {
                  iTime++
                  iDate = i
                  if (iTime == iDSTStartWeek) {
                    break
                  }
                }
              }
              dtDSTStart.setDate(iDate)
              dtDSTStart.setMonth(iDSTStartMonth - 1)
              const iDSTStopMonth = parseInt(
                szDSTStopTime.split('.')[0].replace('M', ''),
                10
              )
              dtDSTStop.setMonth(iDSTStopMonth - 1)
              const iDSTStopWeek = parseInt(szDSTStopTime.split('.')[1], 10)
              const iDSTStopDay = parseInt(
                szDSTStopTime.split('.')[2].split('/')[0]
              )
              const iDSTStopTime = parseInt(
                szDSTStopTime
                  .split('.')[2]
                  .split('/')[1]
                  .split(':')[0],
                10
              )
              dtDSTStop.setHours(iDSTStopTime)
              iTime = 0
              iDate = 0
              for (var i = 1; i <= 31; i++) {
                dtDSTStop.setDate(i)
                if (dtDSTStop.getMonth() !== iDSTStopMonth - 1) {
                  break
                }
                if (dtDSTStop.getDay() == iDSTStopDay) {
                  iTime++
                  iDate = i
                  if (iTime == iDSTStopWeek) {
                    break
                  }
                }
              }
              dtDSTStop.setDate(iDate)
              dtDSTStop.setMonth(iDSTStopMonth - 1)
              if (dtDSTStart.getTime() < dtDSTStop.getTime()) {
                if (
                  dtDeviceDate.getTime() >= dtDSTStart.getTime() &&
                  dtDeviceDate.getTime() <= dtDSTStop.getTime()
                ) {
                  var szDSTTime = szTimeZone.substring(
                    iDSTPos + 3,
                    iDSTPos + 11
                  )
                  iDSTTime =
                    parseInt(szDSTTime.split(':')[0], 10) * 60 +
                    parseInt(szDSTTime.split(':')[1], 10)
                }
              } else {
                if (
                  dtDeviceDate.getTime() >= dtDSTStart.getTime() ||
                  dtDeviceDate.getTime() <= dtDSTStop.getTime()
                ) {
                  var szDSTTime = szTimeZone.substring(
                    iDSTPos + 3,
                    iDSTPos + 11
                  )
                  iDSTTime =
                    parseInt(szDSTTime.split(':')[0], 10) * 60 +
                    parseInt(szDSTTime.split(':')[1], 10)
                }
              }
            }
            const arDTZms = szTimeZone.match(/\D+([+-])(\d+):(\d+):(\d+)/)
            if (arDTZms.length == 5) {
              const dtNow = new Date()
              const iLocalOffsetMin = dtNow.getTimezoneOffset()
              let iDeviceOffsetMin =
                parseInt(arDTZms[2]) * 60 + parseInt(arDTZms[3])
              iDeviceOffsetMin =
                arDTZms[1] === '+' ? iDeviceOffsetMin : -iDeviceOffsetMin
              iDeviceMinusLocalTime =
                (iLocalOffsetMin - iDeviceOffsetMin + iDSTTime) * 60 * 1e3
            }
            oDeviceInfo.iDeviceMinusLocalTime = iDeviceMinusLocalTime
            resolve(iDeviceMinusLocalTime)
          },
          error: () => {
            reject()
          }
        }
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          m_ISAPIProtocol.CGI.time,
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.getAnalogChannelInfo = function (
      oDeviceInfo,
      options
    ) {
      const oPromise = new Promise(function (resolve, reject) {
        const newOptions = {}
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function (xmlData) {
            const arrXml = []
            arrXml.push('<VideoInputChannelList>')
            const nodeList = NS.$XML(xmlData).find('VideoInputChannel', true)
            oDeviceInfo.iAnalogChannelNum = nodeList.length
            for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
              const node = nodeList[i]
              arrXml.push('<VideoInputChannel>')
              arrXml.push(
                '<id>' +
                  NS.$XML(node)
                    .find('id')
                    .eq(0)
                    .text() +
                  '</id>'
              )
              arrXml.push(
                '<inputPort>' +
                  NS.$XML(node)
                    .find('inputPort')
                    .eq(0)
                    .text() +
                  '</inputPort>'
              )
              arrXml.push(
                '<name>' +
                  m_utilsInc.escape(
                    NS.$XML(node)
                      .find('name')
                      .eq(0)
                      .text()
                  ) +
                  '</name>'
              )
              arrXml.push(
                '<videoFormat>' +
                  NS.$XML(node)
                    .find('videoFormat')
                    .eq(0)
                    .text() +
                  '</videoFormat>'
              )
              arrXml.push('</VideoInputChannel>')
            }
            arrXml.push('</VideoInputChannelList>')
            const xmlDoc = m_utilsInc.loadXML(arrXml.join(''))
            if (options.success) {
              options.success(xmlDoc)
            }
            resolve(xmlDoc)
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          m_ISAPIProtocol.CGI.getAnalogChannelInfo,
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.getDigitalChannel = function (oDeviceInfo, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const newOptions = {}
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function (xmlData) {
            const arrXml = []
            arrXml.push('<InputProxyChannelList>')
            const nodeList = NS.$XML(xmlData).find('InputProxyChannel', true)
            for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
              const node = nodeList[i]
              arrXml.push('<InputProxyChannel>')
              arrXml.push(
                '<id>' +
                  NS.$XML(node)
                    .find('id')
                    .eq(0)
                    .text() +
                  '</id>'
              )
              arrXml.push(
                '<name>' +
                  m_utilsInc.escape(
                    NS.$XML(node)
                      .find('name')
                      .eq(0)
                      .text()
                  ) +
                  '</name>'
              )
              arrXml.push('</InputProxyChannel>')
            }
            arrXml.push('</InputProxyChannelList>')
            const xmlDoc = m_utilsInc.loadXML(arrXml.join(''))
            if (options.success) {
              options.success(xmlDoc)
            }
            resolve(xmlDoc)
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          m_ISAPIProtocol.CGI.getDigitalChannel,
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.getDigitalChannelInfo = function (
      oDeviceInfo,
      options
    ) {
      const oPromise = new Promise(async (resolve, reject) => {
        let oDigitalChannelXML = null
        const oDigitalChannelName = {}
        try {
          oDigitalChannelXML = await m_ISAPIProtocol.getDigitalChannel(
            oDeviceInfo,
            {}
          )
        } catch (oError) {
          reject(oError)
        }
        const nodeList = NS.$XML(oDigitalChannelXML).find(
          'InputProxyChannel',
          true
        )
        for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
          const node = nodeList[i]
          const szId = NS.$XML(node)
            .find('id')
            .eq(0)
            .text()
          const szName = NS.$XML(node)
            .find('name')
            .eq(0)
            .text()
          oDigitalChannelName[szId] = szName
        }
        const newOptions = {}
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function (xmlData) {
            const arrXml = []
            arrXml.push('<InputProxyChannelStatusList>')
            const nodeList = NS.$XML(xmlData).find(
              'InputProxyChannelStatus',
              true
            )
            for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
              const node = nodeList[i]
              const szId = NS.$XML(node)
                .find('id')
                .eq(0)
                .text()
              arrXml.push('<InputProxyChannelStatus>')
              arrXml.push('<id>' + szId + '</id>')
              arrXml.push('<sourceInputPortDescriptor>')
              arrXml.push(
                '<proxyProtocol>' +
                  NS.$XML(node)
                    .find('proxyProtocol')
                    .eq(0)
                    .text() +
                  '</proxyProtocol>'
              )
              arrXml.push(
                '<addressingFormatType>' +
                  NS.$XML(node)
                    .find('addressingFormatType')
                    .eq(0)
                    .text() +
                  '</addressingFormatType>'
              )
              arrXml.push(
                '<ipAddress>' +
                  NS.$XML(node)
                    .find('ipAddress')
                    .eq(0)
                    .text() +
                  '</ipAddress>'
              )
              arrXml.push(
                '<managePortNo>' +
                  NS.$XML(node)
                    .find('managePortNo')
                    .eq(0)
                    .text() +
                  '</managePortNo>'
              )
              arrXml.push(
                '<srcInputPort>' +
                  NS.$XML(node)
                    .find('srcInputPort')
                    .eq(0)
                    .text() +
                  '</srcInputPort>'
              )
              arrXml.push(
                '<userName>' +
                  m_utilsInc.escape(
                    NS.$XML(node)
                      .find('userName')
                      .eq(0)
                      .text()
                  ) +
                  '</userName>'
              )
              arrXml.push(
                '<streamType>' +
                  NS.$XML(node)
                    .find('streamType')
                    .eq(0)
                    .text() +
                  '</streamType>'
              )
              arrXml.push(
                '<online>' +
                  NS.$XML(node)
                    .find('online')
                    .eq(0)
                    .text() +
                  '</online>'
              )
              arrXml.push(
                '<name>' +
                  m_utilsInc.escape(oDigitalChannelName[szId]) +
                  '</name>'
              )
              arrXml.push('</sourceInputPortDescriptor>')
              arrXml.push('</InputProxyChannelStatus>')
            }
            arrXml.push('</InputProxyChannelStatusList>')
            const xmlDoc = m_utilsInc.loadXML(arrXml.join(''))
            if (options.success) {
              options.success(xmlDoc)
            }
            resolve(xmlDoc)
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          m_ISAPIProtocol.CGI.getDigitalChannelInfo,
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.getZeroChannelInfo = function (
      oDeviceInfo,
      options
    ) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.getZeroChannelInfo,
        options
      )
    }
    ISAPIProtocol.prototype.getStreamChannels = function (oDeviceInfo, options) {
      if (oDeviceInfo.iAnalogChannelNum != 0) {
        var url = m_utilsInc.formatString(
          this.CGI.getStreamChannels.analog,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort
        )
      } else {
        var url = m_utilsInc.formatString(
          this.CGI.getStreamChannels.digital,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort
        )
      }
      let szURI
      if (oDeviceInfo.iAnalogChannelNum != 0) {
        szURI = this.CGI.getStreamChannels.analog
      } else {
        szURI = this.CGI.getStreamChannels.digital
      }
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        szURI,
        options
      )
    }
    ISAPIProtocol.prototype.getPPPoEStatus = function (oDeviceInfo, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.getPPPoEStatus,
        options
      )
    }
    ISAPIProtocol.prototype.getUPnPPortStatus = function (oDeviceInfo, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.getUPnPPortStatus,
        options
      )
    }
    ISAPIProtocol.prototype.getNetworkBond = function (oDeviceInfo, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.getNetworkBond,
        options
      )
    }
    ISAPIProtocol.prototype.getNetworkInterface = function (
      oDeviceInfo,
      options
    ) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.getNetworkInterface,
        options
      )
    }
    ISAPIProtocol.prototype.getPortInfo = function (oDeviceInfo, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.getPortInfo,
        options
      )
    }
    ISAPIProtocol.prototype.startRealPlay = function (oDeviceInfo, options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const iChannelID = options.iChannelID * 100 + options.iStreamType
        let szUrl = ''
        const szRtspIP = m_utilsInc.delPort(oDeviceInfo.szIP)
        let iRtspPort = oDeviceInfo.iRtspPort
        if (options.iPort) {
          iRtspPort = options.iPort
        }
        if (options.bZeroChannel) {
          szUrl = m_utilsInc.formatString(
            oDeviceInfo.oProtocolInc.CGI.startRealPlay.zeroChannels,
            szRtspIP,
            iRtspPort,
            iChannelID
          )
        } else {
          szUrl = m_utilsInc.formatString(
            oDeviceInfo.oProtocolInc.CGI.startRealPlay.channels,
            szRtspIP,
            iRtspPort,
            iChannelID
          )
        }
        const addToWndSet = function () {
          const wndInfo = new wndInfoClass()
          wndInfo.iIndex = options.iWndIndex
          wndInfo.szIP = oDeviceInfo.szIP
          wndInfo.iCGIPort = oDeviceInfo.iCGIPort
          wndInfo.szDeviceIdentify = oDeviceInfo.szDeviceIdentify
          wndInfo.iChannelID = options.iChannelID
          wndInfo.iPlayStatus = PLAY_STATUS_REALPLAY
          m_wndSet.push(wndInfo)
        }
        await m_pluginOBJECT.JS_SetSecretKey(0, m_oLocalCfg.secretKey, 1)
        m_pluginOBJECT
          .JS_Play(
            szUrl,
            { auth: oDeviceInfo.szAuth, userInfo: oDeviceInfo.szAuth },
            options.iWndIndex,
            '',
            '',
            options.bFlag
          )
          .then(
            () => {
              addToWndSet()
              resolve()
            },
            () => {
              reject()
            }
          )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.startPlay = function (oDeviceInfo, options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const addToWndSet = function () {
          const wndInfo = new wndInfoClass()
          wndInfo.iIndex = options.iWndIndex
          wndInfo.szIP = oDeviceInfo.szIP
          wndInfo.szDeviceIdentify = oDeviceInfo.szDeviceIdentify
          wndInfo.iPlayStatus = PLAY_STATUS_PLAYBACK
          m_wndSet.push(wndInfo)
        }
        m_pluginOBJECT
          .JS_Play(
            options.szUrl,
            { auth: oDeviceInfo.szAuth, userInfo: oDeviceInfo.szAuth },
            options.iWndIndex,
            options.startTime,
            options.endTime,
            true
          )
          .then(
            () => {
              addToWndSet()
              resolve()
            },
            () => {
              reject()
            }
          )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.startVoiceTalk = function (
      oDeviceInfo,
      iAudioChannel
    ) {
      const szOpenUrl = m_utilsInc.formatString(
        this.CGI.startVoiceTalk.open,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort,
        iAudioChannel
      )
      const szCloseUrl = m_utilsInc.formatString(
        this.CGI.startVoiceTalk.close,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort,
        iAudioChannel
      )
      const szAudioDataUrl = m_utilsInc.formatString(
        this.CGI.startVoiceTalk.audioData,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort,
        iAudioChannel
      )
      return m_pluginOBJECT.JS_StartTalk(
        szOpenUrl,
        szCloseUrl,
        szAudioDataUrl,
        oDeviceInfo.szAuth,
        oDeviceInfo.iAudioType,
        oDeviceInfo.m_iAudioBitRate,
        oDeviceInfo.m_iAudioSamplingRate,
        oDeviceInfo.m_iSoundChan,
        oDeviceInfo.m_iDeviceAudioSoundChan
      )
    }
    ISAPIProtocol.prototype.audioPlay = function (options) {
      return m_pluginOBJECT.JS_AudioPlay(
        options.szUrl,
        options.szAuth,
        -1,
        -1,
        true,
        options.iAudioType
      )
    }
    ISAPIProtocol.prototype.ptzAutoControl = function (
      oDeviceInfo,
      bStop,
      oWndInfo,
      options
    ) {
      const oPromise = new Promise((resolve, reject) => {
        const iChannelID = oWndInfo.iChannelID
        let szUrl = ''
        let szData = ''
        options.iPTZSpeed =
          options.iPTZSpeed < 7 ? options.iPTZSpeed * 15 : 100
        if (bStop) {
          options.iPTZSpeed = 0
        }
        if (iChannelID <= oDeviceInfo.iAnalogChannelNum) {
          szUrl = m_utilsInc.formatString(
            m_ISAPIProtocol.CGI.ptzAutoControl.analog,
            oDeviceInfo.szHttpProtocol,
            oDeviceInfo.szIP,
            oDeviceInfo.iCGIPort,
            oWndInfo.iChannelID
          )
        } else {
          szUrl = m_utilsInc.formatString(
            m_ISAPIProtocol.CGI.ptzAutoControl.digital,
            oDeviceInfo.szHttpProtocol,
            oDeviceInfo.szIP,
            oDeviceInfo.iCGIPort,
            oWndInfo.iChannelID
          )
        }
        szData =
          "<?xml version='1.0' encoding='UTF-8'?>" +
          '<autoPanData>' +
          '<autoPan>' +
          options.iPTZSpeed +
          '</autoPan>' +
          '</autoPanData>'
        const newOptions = {
          type: 'PUT',
          url: szUrl,
          data: szData,
          success: null,
          error: null
        }
        const self = this
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function () {
            oWndInfo.bPTZAuto = !oWndInfo.bPTZAuto
            if (options.success) {
              options.success()
            }
            resolve()
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          '',
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.ptzControl = function (
      oDeviceInfo,
      bStop,
      oWndInfo,
      options
    ) {
      const iChannelID = oWndInfo.iChannelID
      let szUrl = ''
      if (oWndInfo.bPTZAuto) {
        this.ptzAutoControl(oDeviceInfo, true, oWndInfo, { iPTZSpeed: 0 })
      }
      if (bStop) {
        options.iPTZSpeed = 0
      } else {
        options.iPTZSpeed =
          options.iPTZSpeed < 7 ? options.iPTZSpeed * 15 : 100
      }
      const oDirection = [
        {},
        { pan: 0, tilt: options.iPTZSpeed },
        { pan: 0, tilt: -options.iPTZSpeed },
        { pan: -options.iPTZSpeed, tilt: 0 },
        { pan: options.iPTZSpeed, tilt: 0 },
        { pan: -options.iPTZSpeed, tilt: options.iPTZSpeed },
        { pan: -options.iPTZSpeed, tilt: -options.iPTZSpeed },
        { pan: options.iPTZSpeed, tilt: options.iPTZSpeed },
        { pan: options.iPTZSpeed, tilt: -options.iPTZSpeed },
        {},
        { speed: options.iPTZSpeed },
        { speed: -options.iPTZSpeed },
        { speed: options.iPTZSpeed },
        { speed: -options.iPTZSpeed },
        { speed: options.iPTZSpeed },
        { speed: -options.iPTZSpeed }
      ]
      let szData = ''
      let oCommond = {}
      switch (options.iPTZIndex) {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
          oCommond = this.CGI.ptzControl
          szData =
            "<?xml version='1.0' encoding='UTF-8'?>" +
            '<PTZData>' +
            '<pan>' +
            oDirection[options.iPTZIndex].pan +
            '</pan>' +
            '<tilt>' +
            oDirection[options.iPTZIndex].tilt +
            '</tilt>' +
            '</PTZData>'
          break
        case 10:
        case 11:
          oCommond = this.CGI.ptzControl
          szData =
            "<?xml version='1.0' encoding='UTF-8'?>" +
            '<PTZData>' +
            '<zoom>' +
            oDirection[options.iPTZIndex].speed +
            '</zoom>' +
            '</PTZData>'
          break
        case 12:
        case 13:
          oCommond = this.CGI.ptzFocus
          szData =
            "<?xml version='1.0' encoding='UTF-8'?>" +
            '<FocusData>' +
            '<focus>' +
            oDirection[options.iPTZIndex].speed +
            '</focus>' +
            '</FocusData>'
          break
        case 14:
        case 15:
          oCommond = this.CGI.ptzIris
          szData =
            "<?xml version='1.0' encoding='UTF-8'?>" +
            '<IrisData>' +
            '<iris>' +
            oDirection[options.iPTZIndex].speed +
            '</iris>' +
            '</IrisData>'
          break
        default:
          return
      }
      if (iChannelID <= oDeviceInfo.iAnalogChannelNum) {
        szUrl = m_utilsInc.formatString(
          oCommond.analog,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID
        )
      } else {
        szUrl = m_utilsInc.formatString(
          oCommond.digital,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID
        )
      }
      const newOptions = {
        type: 'PUT',
        url: szUrl,
        data: szData,
        success: null,
        error: null
      }
      m_utilsInc.extend(newOptions, options)
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        '',
        newOptions
      )
    }
    ISAPIProtocol.prototype.setPreset = function (
      oDeviceInfo,
      oWndInfo,
      options
    ) {
      const iChannelID = oWndInfo.iChannelID
      let szUrl = ''
      let szData = ''
      if (iChannelID <= oDeviceInfo.iAnalogChannelNum) {
        szUrl = m_utilsInc.formatString(
          this.CGI.setPreset.analog,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID,
          options.iPresetID
        )
      } else {
        szUrl = m_utilsInc.formatString(
          this.CGI.setPreset.digital,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID,
          options.iPresetID
        )
      }
      szData = "<?xml version='1.0' encoding='UTF-8'?>"
      szData += '<PTZPreset>'
      szData += '<id>' + options.iPresetID + '</id>'
      if (oDeviceInfo.szDeviceType != DEVICE_TYPE_IPDOME) {
        szData +=
          '<presetName>' + 'Preset' + options.iPresetID + '</presetName>'
      }
      szData += '</PTZPreset>'
      const newOptions = {
        type: 'PUT',
        url: szUrl,
        data: szData,
        success: null,
        error: null
      }
      m_utilsInc.extend(newOptions, options)
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        '',
        newOptions
      )
    }
    ISAPIProtocol.prototype.goPreset = function (
      oDeviceInfo,
      oWndInfo,
      options
    ) {
      const iChannelID = oWndInfo.iChannelID
      let szUrl = ''
      if (iChannelID <= oDeviceInfo.iAnalogChannelNum) {
        szUrl = m_utilsInc.formatString(
          this.CGI.goPreset.analog,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID,
          options.iPresetID
        )
      } else {
        szUrl = m_utilsInc.formatString(
          this.CGI.goPreset.digital,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID,
          options.iPresetID
        )
      }
      const newOptions = {
        type: 'PUT',
        url: szUrl,
        success: null,
        error: null
      }
      m_utilsInc.extend(newOptions, options)
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        '',
        newOptions
      )
    }
    ISAPIProtocol.prototype.recordSearch = function (oDeviceInfo, options) {
      const oPromise = new Promise((resolve, reject) => {
        let szUrl = ''
        let szData = ''
        const iChannelID = options.iChannelID
        const iStreamType = options.iStreamType
        const szStartTime = options.szStartTime.replace(' ', 'T') + 'Z'
        const szEndTime = options.szEndTime.replace(' ', 'T') + 'Z'
        szUrl = m_utilsInc.formatString(
          m_ISAPIProtocol.CGI.recordSearch,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort
        )
        szData =
          "<?xml version='1.0' encoding='UTF-8'?>" +
          '<CMSearchDescription>' +
          '<searchID>' +
          new UUID() +
          '</searchID>' +
          '<trackList><trackID>' +
          (iChannelID * 100 + iStreamType) +
          '</trackID></trackList>' +
          '<timeSpanList>' +
          '<timeSpan>' +
          '<startTime>' +
          szStartTime +
          '</startTime>' +
          '<endTime>' +
          szEndTime +
          '</endTime>' +
          '</timeSpan>' +
          '</timeSpanList>' +
          '<maxResults>50</maxResults>' +
          '<searchResultPostion>' +
          options.iSearchPos +
          '</searchResultPostion>' +
          '<metadataList>' +
          '<metadataDescriptor>//metadata.ISAPI.org/VideoMotion</metadataDescriptor>' +
          '</metadataList>' +
          '</CMSearchDescription>'
        const httpClient = new HttpPluginClient()
        const newOptions = {
          type: 'POST',
          url: szUrl,
          data: szData,
          success: null,
          error: null
        }
        m_utilsInc.extend(newOptions, options)
        m_utilsInc.extend(newOptions, {
          success: function (xmlDoc) {
            const arrXml = []
            arrXml.push('<CMSearchResult>')
            arrXml.push(
              '<responseStatus>' +
                NS.$XML(xmlDoc)
                  .find('responseStatus')
                  .eq(0)
                  .text() +
                '</responseStatus>'
            )
            arrXml.push(
              '<responseStatusStrg>' +
                NS.$XML(xmlDoc)
                  .find('responseStatusStrg')
                  .eq(0)
                  .text() +
                '</responseStatusStrg>'
            )
            arrXml.push(
              '<numOfMatches>' +
                NS.$XML(xmlDoc)
                  .find('numOfMatches')
                  .eq(0)
                  .text() +
                '</numOfMatches>'
            )
            arrXml.push('<matchList>')
            const nodeList = NS.$XML(xmlDoc).find('searchMatchItem', true)
            for (let i = 0, iLen = nodeList.length; i < iLen; i++) {
              const node = nodeList[i]
              arrXml.push('<searchMatchItem>')
              arrXml.push(
                '<trackID>' +
                  NS.$XML(node)
                    .find('trackID')
                    .eq(0)
                    .text() +
                  '</trackID>'
              )
              arrXml.push(
                '<startTime>' +
                  NS.$XML(node)
                    .find('startTime')
                    .eq(0)
                    .text() +
                  '</startTime>'
              )
              arrXml.push(
                '<endTime>' +
                  NS.$XML(node)
                    .find('endTime')
                    .eq(0)
                    .text() +
                  '</endTime>'
              )
              arrXml.push(
                '<playbackURI>' +
                  m_utilsInc.escape(
                    NS.$XML(node)
                      .find('playbackURI')
                      .eq(0)
                      .text()
                  ) +
                  '</playbackURI>'
              )
              arrXml.push(
                '<metadataDescriptor>' +
                  NS.$XML(node)
                    .find('metadataDescriptor')
                    .eq(0)
                    .text()
                    .split('/')[1] +
                  '</metadataDescriptor>'
              )
              arrXml.push('</searchMatchItem>')
            }
            arrXml.push('</matchList>')
            arrXml.push('</CMSearchResult>')
            xmlDoc = m_utilsInc.loadXML(arrXml.join(''))
            if (options.success) {
              options.success(xmlDoc)
            }
            resolve(xmlDoc)
          },
          error: function (oError) {
            if (options.error) {
              options.error(oError)
            }
            reject(oError)
          }
        })
        m_webVideoCtrl.I_SendHTTPRequest(
          oDeviceInfo.szDeviceIdentify,
          '',
          newOptions
        )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.startPlayback = function (oDeviceInfo, options) {
      const oPromise = new Promise(async function (resolve, reject) {
        const iWndIndex = options.iWndIndex
        let szUrl = ''
        const szStartTime = options.szStartTime
        const szEndTime = options.szEndTime
        const szRtspIP = m_utilsInc.delPort(oDeviceInfo.szIP)
        let iRtspPort = oDeviceInfo.iRtspPort
        if (options.iPort) {
          iRtspPort = options.iPort
        }
        szUrl = m_utilsInc.formatString(
          options.cgi,
          szRtspIP,
          iRtspPort,
          options.iChannelID
        )
        if (!m_utilsInc.isUndefined(options.oTransCodeParam)) {
          const szTransStreamXml = _generateTransCodeXml(
            options.oTransCodeParam
          )
          if (szTransStreamXml == '') {
            return -1
          }
          m_pluginOBJECT.JS_SetTrsPlayBackParam(iWndIndex, szTransStreamXml)
        }
        const addToWndSet = function () {
          const wndInfo = new wndInfoClass()
          wndInfo.iIndex = iWndIndex
          wndInfo.szIP = oDeviceInfo.szIP
          wndInfo.iCGIPort = oDeviceInfo.iCGIPort
          wndInfo.szDeviceIdentify = oDeviceInfo.szDeviceIdentify
          wndInfo.iChannelID = options.iChannelID
          wndInfo.iPlayStatus = PLAY_STATUS_PLAYBACK
          m_wndSet.push(wndInfo)
        }
        m_pluginOBJECT
          .JS_Play(
            szUrl,
            { auth: oDeviceInfo.szAuth, userInfo: oDeviceInfo.szAuth },
            iWndIndex,
            szStartTime,
            szEndTime,
            options.bFlag
          )
          .then(
            () => {
              addToWndSet()
              resolve()
            },
            () => {
              reject()
            }
          )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.reversePlayback = function (oDeviceInfo, options) {
      const oPromise = new Promise(function (resolve, reject) {
        const iWndIndex = options.iWndIndex
        const szStartTime = options.szStartTime
        const szEndTime = options.szEndTime
        const szRtspIP = m_utilsInc.delPort(oDeviceInfo.szIP)
        let iRtspPort = oDeviceInfo.iRtspPort
        if (options.iPort) {
          iRtspPort = options.iPort
        }
        const szUrl = m_utilsInc.formatString(
          options.cgi,
          szRtspIP,
          iRtspPort,
          options.iChannelID
        )
        m_pluginOBJECT
          .JS_ReversePlay(
            szUrl,
            { auth: oDeviceInfo.szAuth, userInfo: oDeviceInfo.szAuth },
            iWndIndex,
            szStartTime,
            szEndTime
          )
          .then(
            () => {
              const wndInfo = new wndInfoClass()
              wndInfo.iIndex = iWndIndex
              wndInfo.szIP = oDeviceInfo.szIP
              wndInfo.iCGIPort = oDeviceInfo.iCGIPort
              wndInfo.szDeviceIdentify = oDeviceInfo.szDeviceIdentify
              wndInfo.iChannelID = options.iChannelID
              wndInfo.iPlayStatus = PLAY_STATUS_REVERSE_PLAYBACK
              m_wndSet.push(wndInfo)
              resolve()
            },
            () => {
              reject()
            }
          )
      })
      return oPromise
    }
    ISAPIProtocol.prototype.startDownloadRecord = function (
      oDeviceInfo,
      options
    ) {
      const szUrl = m_utilsInc.formatString(
        this.CGI.startDownloadRecord,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort
      )
      const szDownXml =
        "<?xml version='1.0' encoding='UTF-8'?>" +
        '<downloadRequest>' +
        '<playbackURI>' +
        m_utilsInc.escape(options.szPlaybackURI) +
        '</playbackURI>' +
        '</downloadRequest>'
      return m_pluginOBJECT.JS_StartAsyncDownload(
        szUrl,
        oDeviceInfo.szAuth,
        options.szFileName,
        szDownXml,
        options.bDateDir
      )
    }
    ISAPIProtocol.prototype.exportDeviceConfig = function (oDeviceInfo) {
      const szUrl = m_utilsInc.formatString(
        this.CGI.downloaddeviceConfig,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort
      )
      return m_pluginOBJECT.JS_DownloadFile(szUrl, oDeviceInfo.szAuth, '', 0)
    }
    ISAPIProtocol.prototype.importDeviceConfig = function (
      oDeviceInfo,
      options
    ) {
      const szUrl = m_utilsInc.formatString(
        this.CGI.uploaddeviceConfig,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort
      )
      return m_pluginOBJECT.JS_StartAsynUpload(
        szUrl,
        '',
        oDeviceInfo.szAuth,
        options.szFileName,
        0
      )
    }
    ISAPIProtocol.prototype.restart = function (oDeviceInfo, options) {
      const newOptions = { type: 'PUT', success: null, error: null }
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.restart,
        newOptions
      )
    }
    ISAPIProtocol.prototype.restore = function (oDeviceInfo, szMode, options) {
      const szUrl = m_utilsInc.formatString(
        this.CGI.restore,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort,
        szMode
      )
      const newOptions = {
        type: 'PUT',
        url: szUrl,
        success: null,
        error: null
      }
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        '',
        newOptions
      )
    }
    ISAPIProtocol.prototype.startUpgrade = function (oDeviceInfo, szFileName) {
      const szUpgradeURL = m_utilsInc.formatString(
        this.CGI.startUpgrade.upgrade,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort
      )
      const szStatusURL = m_utilsInc.formatString(
        this.CGI.startUpgrade.status,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort
      )
      return m_pluginOBJECT.JS_StartUpgrade(
        szUpgradeURL,
        '',
        oDeviceInfo.szAuth,
        szFileName
      )
    }
    ISAPIProtocol.prototype.set3DZoom = function (
      oDeviceInfo,
      oWndInfo,
      oPoints,
      options
    ) {
      const iChannelID = oWndInfo.iChannelID
      let szUrl = ''
      if (iChannelID <= oDeviceInfo.iAnalogChannelNum) {
        szUrl = m_utilsInc.formatString(
          this.CGI.set3DZoom.analog,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID
        )
      } else {
        szUrl = m_utilsInc.formatString(
          this.CGI.set3DZoom.digital,
          oDeviceInfo.szHttpProtocol,
          oDeviceInfo.szIP,
          oDeviceInfo.iCGIPort,
          oWndInfo.iChannelID
        )
      }
      if (
        oPoints[0][0] === 0 &&
        oPoints[0][1] === 0 &&
        !(oPoints[2][0] === 0 && oPoints[2][1] === 0)
      ) {
        oPoints[0][0] = oPoints[2][0]
        oPoints[0][1] = oPoints[2][1]
      }
      const szXml =
        "<?xml version='1.0' encoding='UTF-8'?><Position3D><StartPoint>" +
        '<positionX>' +
        parseInt(oPoints[0][0] * 255, 10) +
        '</positionX>' +
        '<positionY>' +
        (255 - parseInt(oPoints[0][1] * 255, 10)) +
        '</positionY>' +
        '</StartPoint><EndPoint><positionX>' +
        parseInt(oPoints[2][0] * 255, 10) +
        '</positionX>' +
        '<positionY>' +
        (255 - parseInt(oPoints[2][1] * 255, 10)) +
        '</positionY></EndPoint></Position3D>'
      const httpClient = new HttpPluginClient()
      const newOptions = {
        type: 'PUT',
        url: szUrl,
        data: szXml,
        success: null,
        error: null
      }
      m_utilsInc.extend(newOptions, options)
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        '',
        newOptions
      )
    }
    ISAPIProtocol.prototype.getSDKCapa = function (oDeviceInfo, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.SDKCapabilities,
        options
      )
    }
    ISAPIProtocol.prototype.deviceCapturePic = function (
      oDeviceInfo,
      iChannelID,
      szPicName,
      options
    ) {
      var iChannelID = iChannelID * 100 + 1
      let iRet = -1
      let szUrl = m_utilsInc.formatString(
        this.CGI.deviceCapture.channels,
        oDeviceInfo.szHttpProtocol,
        oDeviceInfo.szIP,
        oDeviceInfo.iCGIPort,
        iChannelID
      )
      const aQuery = []
      if (m_utilsInc.isInt(options.iResolutionWidth)) {
        aQuery.push('videoResolutionWidth=' + options.iResolutionWidth)
      }
      if (m_utilsInc.isInt(options.iResolutionHeight)) {
        aQuery.push('videoResolutionHeight=' + options.iResolutionHeight)
      }
      if (aQuery.length > 0) {
        szUrl += '?' + aQuery.join('&')
      }
      const JDeviceCapturePic = function (szUrl, szFileName) {
        const szFileFormat = '.jpg'
        $('body').append(
          '<a id="jsplugin_download_a" href="' +
            szUrl +
            '" download=' +
            szFileName +
            szFileFormat +
            '><li id="jsplugin_download_li"></li></a>'
        )
        $('#jsplugin_download_li').trigger('click')
        $('#jsplugin_download_a').remove()
        return 0
      }
      iRet = JDeviceCapturePic(szUrl, szPicName)
      return iRet
    }
    ISAPIProtocol.prototype.digestLogin = function (
      szIP,
      iProtocol,
      iPort,
      szUserName,
      szPassword,
      options
    ) {
      let szHttpProtocol = ''
      if (iProtocol == 2) {
        szHttpProtocol = 'https://'
      } else {
        szHttpProtocol = 'http://'
      }
      const szUrl = m_utilsInc.formatString(
        this.CGI.login,
        szHttpProtocol,
        szIP,
        iPort
      )
      const newOptions = {
        type: 'GET',
        url: szUrl,
        auth: m_utilsInc.Base64.encode(':' + szUserName + ':' + szPassword),
        success: null,
        error: null
      }
      const szDeviceIdentify = szIP + '_' + iPort
      m_utilsInc.extend(newOptions, options)
      return m_webVideoCtrl.I_SendHTTPRequest(szDeviceIdentify, '', newOptions)
    }
    ISAPIProtocol.prototype.getSystemCapa = function (oDeviceInfo, options) {
      return m_webVideoCtrl.I_SendHTTPRequest(
        oDeviceInfo.szDeviceIdentify,
        this.CGI.systemCapabilities,
        options
      )
    };
    (function (wvc) {
      const XML = function (xd) {
        this.elems = []
        this.length = 0
        this.length = this.elems.push(xd)
      }
      XML.prototype.find = function (szNodeName, bRet) {
        const oXmlNode = this.elems[this.length - 1]
          ? this.elems[this.length - 1].getElementsByTagName(szNodeName)
          : []
        this.length = this.elems.push(oXmlNode)
        if (bRet) {
          return oXmlNode
        } else {
          return this
        }
      }
      XML.prototype.eq = function (i, bRet) {
        const iLen = this.elems[this.length - 1].length
        let oXmlNode = null
        if (iLen > 0 && i < iLen) {
          oXmlNode = this.elems[this.length - 1][i]
        }
        this.length = this.elems.push(oXmlNode)
        if (bRet) {
          return oXmlNode
        } else {
          return this
        }
      }
      XML.prototype.text = function (szText) {
        if (this.elems[this.length - 1]) {
          if (szText) {
            if (window.DOMParser) {
              this.elems[this.length - 1].textContent = szText
            } else {
              this.elems[this.length - 1].text = szText
            }
          } else {
            if (window.DOMParser) {
              return this.elems[this.length - 1].textContent
            } else {
              return this.elems[this.length - 1].text
            }
          }
        } else {
          return ''
        }
      }
      XML.prototype.attr = function (szAttrName) {
        if (this.elems[this.length - 1]) {
          const oAttr = this.elems[this.length - 1].attributes.getNamedItem(
            szAttrName
          )
          if (oAttr) {
            return oAttr.value
          } else {
            return ''
          }
        }
      }
      wvc.$XML = function (xd) {
        return new XML(xd)
      }
    })(this)
    const Utils = function () {}
    Utils.prototype.extend = function () {
      const target = arguments[0] || {}
      let i = 1
      const length = arguments.length
      let options
      for (; i < length; i++) {
        if ((options = arguments[i]) != null) {
          for (const name in options) {
            const src = target[name]
            const copy = options[name]
            if (target === copy) {
              continue
            }
            if (typeof copy === 'object') {
              target[name] = this.extend({}, copy)
            } else if (copy !== undefined) {
              target[name] = copy
            }
          }
        }
      }
      return target
    }
    Utils.prototype.browser = function () {
      const rchrome = /(chrome)[ \/]([\w.]+)/
      const rsafari = /(safari)[ \/]([\w.]+)/
      const ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/
      const rmsie = /(msie) ([\w.]+)/
      const rmsie2 = /(trident.*rv:)([\w.]+)/
      const rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/
      const ua = navigator.userAgent.toLowerCase()
      const match = rchrome.exec(ua) ||
        rsafari.exec(ua) ||
        ropera.exec(ua) ||
        rmsie.exec(ua) ||
        rmsie2.exec(ua) ||
        (ua.indexOf('compatible') < 0 && rmozilla.exec(ua)) || ['unknow', '0']
      if (match.length > 0 && match[1].indexOf('trident') > -1) {
        match[1] = 'msie'
      }
      const oBrowser = {}
      oBrowser[match[1]] = true
      oBrowser.version = match[2]
      return oBrowser
    }
    Utils.prototype.loadXML = function (szXml) {
      if (szXml == null || szXml == '') {
        return null
      }
      let oXmlDoc = null
      if (window.DOMParser) {
        const oParser = new DOMParser()
        oXmlDoc = oParser.parseFromString(szXml, 'text/xml')
      } else {
        oXmlDoc = new ActiveXObject('Microsoft.XMLDOM')
        oXmlDoc.async = false
        oXmlDoc.loadXML(szXml)
      }
      return oXmlDoc
    }
    Utils.prototype.toXMLStr = function (oXmlDoc) {
      let szXmlDoc = ''
      try {
        const oSerializer = new XMLSerializer()
        szXmlDoc = oSerializer.serializeToString(oXmlDoc)
      } catch (e) {
        try {
          szXmlDoc = oXmlDoc.xml
        } catch (e) {
          return ''
        }
      }
      if (szXmlDoc.indexOf('<?xml') == -1) {
        szXmlDoc = "<?xml version='1.0' encoding='utf-8'?>" + szXmlDoc
      }
      return szXmlDoc
    }
    Utils.prototype.escape = function (szStr) {
      if (szStr) {
        return szStr
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
      }
      return szStr
    }
    Utils.prototype.dateFormat = function (oDate, fmt) {
      const o = {
        'M+': oDate.getMonth() + 1,
        'd+': oDate.getDate(),
        'h+': oDate.getHours(),
        'm+': oDate.getMinutes(),
        's+': oDate.getSeconds(),
        'q+': Math.floor((oDate.getMonth() + 3) / 3),
        S: oDate.getMilliseconds()
      }
      if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(
          RegExp.$1,
          (oDate.getFullYear() + '').substr(4 - RegExp.$1.length)
        )
      }
      for (const k in o) {
        if (new RegExp('(' + k + ')').test(fmt)) {
          fmt = fmt.replace(
            RegExp.$1,
            RegExp.$1.length == 1
              ? o[k]
              : ('00' + o[k]).substr(('' + o[k]).length)
          )
        }
      }
      return fmt
    }
    Utils.prototype.Base64 = {
      _keyStr:
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
      encode: function (input) {
        let output = ''
        let chr1, chr2, chr3, enc1, enc2, enc3, enc4
        let i = 0
        input = Utils.prototype.Base64._utf8_encode(input)
        while (i < input.length) {
          chr1 = input.charCodeAt(i++)
          chr2 = input.charCodeAt(i++)
          chr3 = input.charCodeAt(i++)
          enc1 = chr1 >> 2
          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
          enc4 = chr3 & 63
          if (isNaN(chr2)) {
            enc3 = enc4 = 64
          } else if (isNaN(chr3)) {
            enc4 = 64
          }
          output =
            output +
            this._keyStr.charAt(enc1) +
            this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) +
            this._keyStr.charAt(enc4)
        }
        return output
      },
      decode: function (input) {
        let output = ''
        let chr1, chr2, chr3
        let enc1, enc2, enc3, enc4
        let i = 0
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '')
        while (i < input.length) {
          enc1 = this._keyStr.indexOf(input.charAt(i++))
          enc2 = this._keyStr.indexOf(input.charAt(i++))
          enc3 = this._keyStr.indexOf(input.charAt(i++))
          enc4 = this._keyStr.indexOf(input.charAt(i++))
          chr1 = (enc1 << 2) | (enc2 >> 4)
          chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
          chr3 = ((enc3 & 3) << 6) | enc4
          output = output + String.fromCharCode(chr1)
          if (enc3 != 64) {
            output = output + String.fromCharCode(chr2)
          }
          if (enc4 != 64) {
            output = output + String.fromCharCode(chr3)
          }
        }
        output = Utils.prototype.Base64._utf8_decode(output)
        return output
      },
      _utf8_encode: function (string) {
        string = string.replace(/\r\n/g, '\n')
        let utftext = ''
        for (let n = 0; n < string.length; n++) {
          const c = string.charCodeAt(n)
          if (c < 128) {
            utftext += String.fromCharCode(c)
          } else if (c > 127 && c < 2048) {
            utftext += String.fromCharCode((c >> 6) | 192)
            utftext += String.fromCharCode((c & 63) | 128)
          } else {
            utftext += String.fromCharCode((c >> 12) | 224)
            utftext += String.fromCharCode(((c >> 6) & 63) | 128)
            utftext += String.fromCharCode((c & 63) | 128)
          }
        }
        return utftext
      },
      _utf8_decode: function (utftext) {
        let string = ''
        let i = 0
        let c = (c1 = c2 = 0)
        while (i < utftext.length) {
          c = utftext.charCodeAt(i)
          if (c < 128) {
            string += String.fromCharCode(c)
            i++
          } else if (c > 191 && c < 224) {
            c2 = utftext.charCodeAt(i + 1)
            string += String.fromCharCode(((c & 31) << 6) | (c2 & 63))
            i += 2
          } else {
            c2 = utftext.charCodeAt(i + 1)
            c3 = utftext.charCodeAt(i + 2)
            string += String.fromCharCode(
              ((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)
            )
            i += 3
          }
        }
        return string
      }
    }
    Utils.prototype.createEventScript = function (szFor, szEvent, szHtml) {
      const oScript = document.createElement('script')
      oScript.htmlFor = szFor
      oScript.event = szEvent
      oScript.innerHTML = szHtml
      document.body.parentNode.appendChild(oScript)
    }
    Utils.prototype.isInt = function (str) {
      return /^\d+$/.test(str)
    }
    Utils.prototype.getDirName = function () {
      let szDirName = ''
      if (m_options.szBasePath !== '') {
        szDirName = m_options.szBasePath
      } else {
        const szDirNameRegex = /[^?#]*\//
        let oScript = document.getElementById('videonode')
        if (oScript) {
          szDirName = oScript.src.match(szDirNameRegex)[0]
        } else {
          const aScript = document.scripts
          for (let i = 0, iLen = aScript.length; i < iLen; i++) {
            if (aScript[i].src.indexOf('webVideoCtrl.js') > -1) {
              oScript = aScript[i]
              break
            }
          }
          if (oScript) {
            szDirName = oScript.src.match(szDirNameRegex)[0]
          }
        }
      }
      return szDirName
    }
    Utils.prototype.loadScript = function (url, callback) {
      const oScript = document.createElement('script')
      oScript.type = 'text/javascript'
      oScript.onload = function () {
        callback()
      }
      oScript.src = url
      document.getElementsByTagName('head')[0].appendChild(oScript)
    }
    Utils.prototype.cookie = function (key, value, options) {
      if (
        arguments.length > 1 &&
        (value === null || typeof value !== 'object')
      ) {
        options = this.extend({}, options)
        if (value === null) {
          options.expires = -1
        }
        if (typeof options.expires === 'number') {
          const days = options.expires
          const t = (options.expires = new Date())
          t.setDate(t.getDate() + days)
        }
        return (document.cookie = [
          encodeURIComponent(key),
          '=',
          options.raw ? String(value) : encodeURIComponent(String(value)),
          options.expires ? '; expires=' + options.expires.toUTCString() : '',
          options.path ? '; path=' + options.path : '; path=/',
          options.domain ? '; domain=' + options.domain : '',
          options.secure ? '; secure' : ''
        ].join(''))
      }
      options = value || {}
      let result
      const decode = options.raw
        ? function (s) {
          return s
        }
        : decodeURIComponent
      return (result = new RegExp(
        '(?:^|; )' + encodeURIComponent(key) + '=([^;]*)'
      ).exec(document.cookie))
        ? decode(result[1])
        : null
    }
    Utils.prototype.isUndefined = function (o) {
      return typeof o === 'undefined'
    }
    Utils.prototype.isObject = function (o) {
      return Object.prototype.toString.call(o) === '[object Object]'
    }
    Utils.prototype.delPort = function (szIP) {
      const iPos = szIP.indexOf(':')
      if (iPos > -1) {
        return szIP.substring(0, iPos)
      } else {
        return szIP
      }
    }
    Utils.prototype.formatString = function () {
      let string = arguments[0]
      for (let i = 1; i < arguments.length; i++) {
        string = string.replace('%s', arguments[i])
      }
      return string
    }
    Utils.prototype.encodeString = function (str) {
      if (str) {
        return str
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
      } else {
        return ''
      }
    }
    Utils.prototype.formatPolygonXmlToJson = function (szXml) {
      const oXml = this.loadXML(szXml)
      const aPolygonList = []
      let aPoints = []
      const aRect = []
      const aAddPolygon = []
      const aAddRect = []
      let oData
      function colorTransfer (szColor) {
        const iValue = parseInt(szColor, 10)
        let szValue = iValue.toString(16)
        szValue = '0' + szValue
        return szValue.substring(szValue.length - 2)
      }
      $(oXml)
        .find('SnapPolygon')
        .each(function () {
          const iEditType =
            parseInt(
              $(this)
                .find('EditType, editType')
                .text(),
              10
            ) || 0
          const isClose =
            $(this)
              .find('isClosed')
              .text() === 'true'
          const iPolygonType = parseInt(
            $(this)
              .find('polygonType')
              .text(),
            10
          )
          const fShowSquare =
            parseFloat(
              $(this)
                .find('showSquare')
                .text()
            ) || 0
          const szTips =
            $(this)
              .find('tips')
              .text() ||
            $(this)
              .find('Tips')
              .text()
          const iTipsPos =
            parseInt(
              $(this)
                .find('tipsPos')
                .text(),
              10
            ) || 0
          const bShowWH =
            $(this)
              .find('showWH')
              .text() === 'true'
          const szColor =
            '#' +
            colorTransfer(
              $(this)
                .find('r')
                .text()
            ) +
            colorTransfer(
              $(this)
                .find('g')
                .text()
            ) +
            colorTransfer(
              $(this)
                .find('b')
                .text()
            )
          const iMaxPoint =
            parseInt(
              $(this)
                .find('PointNumMax')
                .text(),
              10
            ) - 1
          const iMinPoint =
            parseInt(
              $(this)
                .find('MinClosed')
                .text(),
              10
            ) - 1
          const iId = parseInt(
            $(this)
              .find('id')
              .text(),
            10
          )
          const iRedrawMode =
            parseInt(
              $(this)
                .find('RedrawMode')
                .text(),
              10
            ) || 0
          if (
            $(this)
              .find('pointList')
              .find('point').length === 0
          ) {
            if (iPolygonType === 1) {
              aAddPolygon.push({
                id: iId,
                tips: szTips,
                drawColor: szColor,
                translucent: 0.1,
                maxShapeSupport: 1,
                maxPointSupport: iMaxPoint,
                minPointSupport: iMinPoint,
                showWH: bShowWH,
                redrawMode: iRedrawMode
              })
            } else if (iPolygonType === 0) {
              aAddRect.push({
                id: iId,
                tips: szTips,
                drawColor: szColor,
                translucent: 0.1,
                widthHeightRate: fShowSquare,
                maxShapeSupport: 1,
                type: 1,
                redrawMode: iRedrawMode,
                tipsPos: iTipsPos
              })
            }
          } else {
            aPoints = []
            $(this)
              .find('pointList')
              .find('point')
              .each(function () {
                aPoints.push([
                  parseFloat(
                    $(this)
                      .find('x')
                      .text()
                  ),
                  parseFloat(
                    $(this)
                      .find('y')
                      .text()
                  )
                ])
              })
            oData = {
              id: iId,
              editType: iEditType,
              points: aPoints,
              closed: isClose,
              tips: szTips,
              drawColor: szColor,
              maxPointSupport: iMaxPoint,
              minPointSupport: iMinPoint,
              translucent: 0.1,
              redrawMode: iRedrawMode
            }
            if (iPolygonType === 1) {
              oData.showWH = bShowWH
              aPolygonList.push(oData)
            } else if (iPolygonType === 0) {
              oData.widthHeightRate = fShowSquare
              oData.type = 1
              oData.tipsPos = iTipsPos
              aRect.push(oData)
            }
          }
        })
      return {
        aRect: aRect,
        aPolygon: aPolygonList,
        aAddRect: aAddRect,
        aAddPolygon: aAddPolygon
      }
    }
    Utils.prototype.formatPolygonJsonToXml = function (aData) {
      function colorRgb (szHex) {
        let sColor = szHex.toLowerCase()
        const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
        if (sColor && reg.test(sColor)) {
          let i
          if (sColor.length === 4) {
            let sColorNew = '#'
            for (i = 1; i < 4; i += 1) {
              sColorNew += sColor
                .slice(i, i + 1)
                .concat(sColor.slice(i, i + 1))
            }
            sColor = sColorNew
          }
          const aColorChange = []
          for (i = 1; i < 7; i += 2) {
            aColorChange.push(parseInt('0x' + sColor.slice(i, i + 2), 16))
          }
          return aColorChange
        }
        return [0, 0, 0]
      }
      const aPolygon = aData[0]
      const aRect = aData[1]
      let szXml = "<?xml version='1.0' encoding='utf-8'?><SnapPolygonList>"
      const that = this
      $.each(aPolygon, function (index, oVal) {
        let aColor = [0, 0, 0]
        if (oVal.drawColor) {
          aColor = colorRgb(oVal.drawColor)
        } else {
          aColor = colorRgb('#FF0000')
        }
        szXml += '<SnapPolygon>'
        szXml += '<id>' + oVal.id + '</id>'
        oVal.tips = that.encodeString(oVal.tips)
        if (!oVal.tips) {
          szXml += '<tips></tips>'
        } else {
          szXml += '<tips>' + oVal.tips + '</tips>'
        }
        szXml += '<isClosed>' + oVal.closed.toString() + '</isClosed>'
        szXml +=
          '<color><r>' +
          aColor[0] +
          '</r><g>' +
          aColor[1] +
          '</g><b>' +
          aColor[2] +
          '</b></color>'
        szXml += '<polygonType>1</polygonType>'
        szXml +=
          '<PointNumMax>' +
          (oVal.pointNumMax ? oVal.pointNumMax : 10) +
          '</PointNumMax>'
        szXml +=
          '<MinClosed>' +
          (oVal.minClosed ? oVal.minClosed : 4) +
          '</MinClosed>'
        szXml += '<pointList>'
        $.each(oVal.points, function (i, aVal) {
          szXml +=
            '<point><x>' + aVal[0] + '</x><y>' + aVal[1] + '</y></point>'
        })
        szXml += '</pointList>'
        szXml += '</SnapPolygon>'
      })
      $.each(aRect, function (index, oVal) {
        let aColor = [0, 0, 0]
        if (oVal.drawColor) {
          aColor = colorRgb(oVal.drawColor)
        } else {
          aColor = colorRgb('#FF0000')
        }
        szXml += '<SnapPolygon>'
        szXml += '<id>' + oVal.id + '</id>'
        szXml +=
          '<color><r>' +
          aColor[0] +
          '</r><g>' +
          aColor[1] +
          '</g><b>' +
          aColor[2] +
          '</b></color>'
        szXml += '<polygonType>0</polygonType>'
        oVal.tips = that.encodeString(oVal.tips)
        if (!oVal.tips) {
          szXml += '<tips></tips>'
        } else {
          szXml += '<tips>' + oVal.tips + '</tips>'
        }
        if (typeof oVal.closed !== 'undefined' && oVal.closed !== null) {
          szXml += '<isClosed>' + oVal.closed.toString() + '</isClosed>'
        } else {
          szXml += '<isClosed>true</isClosed>'
        }
        szXml += '<pointList>'
        const aRectTmp = []
        if (oVal.points.length) {
          let iMinX = 2
          let iMaxX = -1
          let iMinY = 2
          let iMaxY = -1
          $.each(oVal.points, function () {
            if (iMinX > this[0]) {
              iMinX = this[0]
            }
            if (iMinY > this[1]) {
              iMinY = this[1]
            }
            if (iMaxX < this[0]) {
              iMaxX = this[0]
            }
            if (iMaxY < this[1]) {
              iMaxY = this[1]
            }
          })
          aRectTmp.push([iMinX, iMinY])
          aRectTmp.push([iMaxX, iMinY])
          aRectTmp.push([iMaxX, iMaxY])
          aRectTmp.push([iMinX, iMaxY])
        }
        $.each(aRectTmp, function (i, aVal) {
          szXml +=
            '<point><x>' + aVal[0] + '</x><y>' + aVal[1] + '</y></point>'
        })
        szXml += '</pointList>'
        szXml += '</SnapPolygon>'
      })
      szXml += '</SnapPolygonList>'
      return szXml
    }
    Utils.prototype.convertToUTCTime = function (szLocalTime, szFormat) {
      if (typeof szFormat === 'undefined') {
        szFormat = 'yyyy-MM-dd hh:mm:ss'
      }
      szLocalTime = szLocalTime.replace('T', ' ').replace('Z', '')
      let _dLocalDate = new Date(Date.parse(szLocalTime.replace(/-/g, '/')))
      _dLocalDate = this.utcDateFormat(_dLocalDate, szFormat)
      _dLocalDate = _dLocalDate.replace(' ', 'T')
      return _dLocalDate
    }
    Utils.prototype.utcDateFormat = function (oDate, fmt) {
      const o = {
        'M+': oDate.getUTCMonth() + 1,
        'd+': oDate.getUTCDate(),
        'h+': oDate.getUTCHours(),
        'm+': oDate.getUTCMinutes(),
        's+': oDate.getUTCSeconds(),
        'q+': Math.floor((oDate.getUTCMonth() + 3) / 3),
        S: oDate.getUTCMilliseconds()
      }
      if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(
          RegExp.$1,
          (oDate.getUTCFullYear() + '').substr(4 - RegExp.$1.length)
        )
      }
      for (const k in o) {
        if (new RegExp('(' + k + ')').test(fmt)) {
          fmt = fmt.replace(
            RegExp.$1,
            RegExp.$1.length == 1
              ? o[k]
              : ('00' + o[k]).substr(('' + o[k]).length)
          )
        }
      }
      return fmt
    }
    Utils.prototype.convertToLocalTime = function (szUTCTime, iDiffTime) {
      szUTCTime = szUTCTime.replace('T', ' ').replace('Z', '')
      if (typeof iDiffTime === 'undefined') {
        iDiffTime = 0
      }
      const szFormat = 'yyyy-MM-dd hh:mm:ss'
      const _aDate = szUTCTime.split(' ')[0].split('-')
      const _iFullYear = parseInt(_aDate[0], 10)
      const _iMonth = parseInt(_aDate[1], 10) - 1
      const _iDay = parseInt(_aDate[2], 10)
      const _aTimes = szUTCTime.split(' ')[1].split(':')
      const _iHour = parseInt(_aTimes[0], 10)
      const _iMinute = parseInt(_aTimes[1], 10)
      const _iSecond = parseInt(_aTimes[2], 10)
      const _dLocalDate = new Date(
        Date.UTC(_iFullYear, _iMonth, _iDay, _iHour, _iMinute, _iSecond)
      )
      _dLocalDate.setTime(_dLocalDate.getTime() + iDiffTime)
      return this.dateFormat(_dLocalDate, szFormat).replace(' ', 'T') + 'Z'
    }
    function UUID () {
      this.id = this.createUUID()
    }
    UUID.prototype.valueOf = function () {
      return this.id
    }
    UUID.prototype.toString = function () {
      return this.id
    }
    UUID.prototype.createUUID = function () {
      const dg = new Date(1582, 10, 15, 0, 0, 0, 0)
      const dc = new Date()
      const t = dc.getTime() - dg.getTime()
      const h = '-'
      const tl = UUID.getIntegerBits(t, 0, 31)
      const tm = UUID.getIntegerBits(t, 32, 47)
      const thv = UUID.getIntegerBits(t, 48, 59) + '1'
      const csar = UUID.getIntegerBits(UUID.rand(4095), 0, 7)
      const csl = UUID.getIntegerBits(UUID.rand(4095), 0, 7)
      const n =
        UUID.getIntegerBits(UUID.rand(8191), 0, 7) +
        UUID.getIntegerBits(UUID.rand(8191), 8, 15) +
        UUID.getIntegerBits(UUID.rand(8191), 0, 7) +
        UUID.getIntegerBits(UUID.rand(8191), 8, 15) +
        UUID.getIntegerBits(UUID.rand(8191), 0, 15)
      return tl + h + tm + h + thv + h + csar + csl + h + n
    }
    UUID.getIntegerBits = function (val, start, end) {
      const base16 = UUID.returnBase(val, 16)
      const quadArray = new Array()
      let quadString = ''
      let i = 0
      for (i = 0; i < base16.length; i++) {
        quadArray.push(base16.substring(i, i + 1))
      }
      for (i = Math.floor(start / 4); i <= Math.floor(end / 4); i++) {
        if (!quadArray[i] || quadArray[i] == '') quadString += '0'
        else quadString += quadArray[i]
      }
      return quadString
    }
    UUID.returnBase = function (number, base) {
      const convert = [
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z'
      ]
      if (number < base) var output = convert[number]
      else {
        const MSD = '' + Math.floor(number / base)
        const LSD = number - MSD * base
        if (MSD >= base) var output = this.returnBase(MSD, base) + convert[LSD]
        else var output = convert[MSD] + convert[LSD]
      }
      return output
    }
    UUID.rand = function (max) {
      return Math.floor(Math.random() * max)
    }
    m_ISAPIProtocol = new ISAPIProtocol()
    m_utilsInc = new Utils()
    return this
  })()
  var NS = (window.WebVideoCtrl = WebVideoCtrl)
  NS.version = '3.3.0'
})(this)
if (typeof exports === 'object' && typeof module !== 'undefined') {
} else if (typeof define === 'function' && define.amd) {
  define(function () {
    return WebVideoCtrl
  })
} else if (typeof define === 'function' && define.cmd) {
  define(function (require, exports, module) {
    module.exports = WebVideoCtrl
  })
} else {
}
